//==================================================================// /* AtomicParsley - main.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (c) 2005-2007 puck_lock with contributions from others; see the CREDITS file ---------------------- Code Contributions by: * Mike Brancato - Debian patches & build support * Brian Story - porting getopt & native Win32 patches */ //==================================================================// #include "AtomicParsley.h" // define one-letter cli options for #define OPT_HELP 'h' #define OPT_TEST 'T' #define OPT_ShowTextData 't' #define OPT_ExtractPix 'E' #define OPT_ExtractPixToPath 'e' #define Meta_artist 'a' #define Meta_artDirector 0xC6 #define Meta_arranger 0xC8 #define Meta_author 0xC9 #define Meta_conductor 0xD0 #define Meta_director 0xD1 #define Meta_originalArtist 0xD2 #define Meta_producer 0xD3 //#define Meta_performer 0xD4 #define Meta_soundEngineer 0xD5 #define Meta_soloist 0xD6 #define Meta_executiveProducer 0xD7 #define Meta_songtitle 's' #define Meta_subtitle 0xC5 #define Meta_album 'b' #define Meta_tracknum 'k' #define Meta_disknum 'd' #define Meta_genre 'g' #define Meta_comment 'c' #define Meta_year 'y' #define Meta_lyrics 'l' #define Meta_lyrics_file 0xC7 #define Meta_composer 'w' #define Meta_copyright 'x' #define Meta_grouping 'G' #define Meta_album_artist 'A' #define Meta_compilation 'C' #define Meta_hdvideo 'O' #define Meta_BPM 'B' #define Meta_artwork 'r' #define Meta_advisory 'V' #define Meta_stik 'S' #define Meta_description 'p' #define Meta_Rating 0xCB #define Meta_longdescription 'j' #define Meta_TV_Network 'n' #define Meta_TV_ShowName 'H' #define Meta_TV_EpisodeNumber 'N' #define Meta_TV_SeasonNumber 'U' #define Meta_TV_Episode 'I' #define Meta_podcastFlag 'f' #define Meta_category 'q' #define Meta_keyword 'K' #define Meta_podcast_URL 'L' #define Meta_podcast_GUID 'J' #define Meta_PurchaseDate 'D' #define Meta_apID 'Y' #define Meta_cnID 0xC0 #define Meta_geID 0xC2 #define Meta_xID 0xC3 #define Meta_storedescription 0xC4 #define Meta_EncodingTool 0xB7 #define Meta_EncodedBy 0xC1 #define Meta_PlayGapless 0xBA #define Meta_SortOrder 0xBF #define Meta_ReverseDNS_Form 'M' #define Meta_rDNS_rating 0xBB #define Meta_StandardDate 'Z' #define Meta_URL 'u' #define Meta_Information 'i' #define Meta_uuid 'z' #define Opt_Extract_all_uuids 0xB8 #define Opt_Extract_a_uuid 0xB9 #define Opt_Ipod_AVC_uuid 0xBE #define Metadata_Purge 'P' #define UserData_Purge 'X' #define foobar_purge '.' #define Meta_dump 'Q' #define Manual_atom_removal 'R' #define Opt_FreeFree 'F' #define OPT_OutputFile 'o' #define OPT_NoOptimize 0xBD #define OPT_OverWrite 'W' #if defined(_WIN32) #define OPT_PreserveTimeStamps 0xCA #endif #define ISO_Copyright 0xAA #define _3GP_Title 0xAB #define _3GP_Author 0xAC #define _3GP_Performer 0xAD #define _3GP_Genre 0xAE #define _3GP_Description 0xAF #define _3GP_Copyright 0xB0 #define _3GP_Album 0xB1 #define _3GP_Year 0xB2 #define _3GP_Rating 0xB3 #define _3GP_Classification 0xB4 #define _3GP_Keyword 0xB5 #define _3GP_Location 0xB6 #define Meta_ID3v2Tag 0xBC #define Meta_movementCount 0xE0 #define Meta_movementName 0xE1 #define Meta_movementNumber 0xE2 #define Meta_showWorkMovement 0xE3 #define Meta_work 0xE4 char *output_file; int total_args; static void kill_signal(int sig); static void kill_signal(int sig) { exit(0); } // less than 80 (max 78) char wide, giving a general (concise) overview static const char *shortHelp_text = "\n" "AtomicParlsey sets metadata into MPEG-4 files & derivatives supporting 3 " "tag\n" " schemes: iTunes-style, 3GPP assets & ISO defined copyright " "notifications.\n" "\n" "AtomicParlsey quick help for setting iTunes-style metadata into MPEG-4 " "files.\n" "\n" "General usage examples:\n" " AtomicParsley /path/to.mp4 -T 1\n" " AtomicParsley /path/to.mp4 -t +\n" " AtomicParsley /path/to.mp4 --artist \"Me\" --artwork /path/to/art.jpg\n" " Atomicparsley /path/to.mp4 --albumArtist \"You\" --podcastFlag true\n" " Atomicparsley /path/to.mp4 --stik \"TV Show\" --advisory explicit\n" "\n" "Getting information about the file & tags:\n" " -T --test Test file for mpeg4-ishness & print atom tree\n" " -t --textdata Prints tags embedded within the file\n" " -E --extractPix Extracts pix to the same folder as the mpeg-4 file\n" "\n" "Setting iTunes-style metadata tags\n" " --artist (string) Set the artist tag\n" " --title (string) Set the title tag\n" " --album (string) Set the album tag\n" " --genre (string) Genre tag (see --longhelp for more info)\n" " --tracknum (num)[/tot] Track number (or track number/total " "tracks)\n" " --disk (num)[/tot] Disk number (or disk number/total disks)\n" " --comment (string) Set the comment tag\n" " --year (num|UTC) Year tag (see --longhelp for \"Release " "Date\")\n" " --lyrics (string) Set lyrics (not subject to 256 byte limit)\n" " --lyricsFile (/path) Set lyrics to the content of a file\n" " --composer (string) Set the composer tag\n" " --copyright (string) Set the copyright tag\n" " --grouping (string) Set the grouping tag\n" " --artwork (/path) Set a piece of artwork (jpeg or png only)\n" " --bpm (number) Set the tempo/bpm\n" " --albumArtist (string) Set the album artist tag\n" " --compilation (boolean) Set the compilation flag (true or false)\n" " --hdvideo (number) Set the hdvideo flag to one of:\n" " false or 0 for standard definition\n" " true or 1 for 720p\n" " 2 for 1080p\n" " --advisory (string*) Content advisory (*values: 'clean', " "'explicit')\n" " --stik (string*) Sets the iTunes \"stik\" atom (see " "--longhelp)\n" " --description (string) Set the description tag\n" " --longdesc (string) Set the long description tag\n" " --storedesc (string) Set the store description tag\n" " --TVNetwork (string) Set the TV Network name\n" " --TVShowName (string) Set the TV Show name\n" " --TVEpisode (string) Set the TV episode/production code\n" " --TVSeasonNum (number) Set the TV Season number\n" " --TVEpisodeNum (number) Set the TV Episode number\n" " --podcastFlag (boolean) Set the podcast flag (true or false)\n" " --category (string) Sets the podcast category\n" " --keyword (string) Sets the podcast keyword\n" " --podcastURL (URL) Set the podcast feed URL\n" " --podcastGUID (URL) Set the episode's URL tag\n" " --purchaseDate (UTC) Set time of purchase\n" " --encodingTool (string) Set the name of the encoder\n" " --encodedBy (string) Set the name of the Person/company who " "encoded the file\n" " --apID (string) Set the Account Name\n" " --cnID (number) Set the iTunes Catalog ID (see --longhelp)\n" " --geID (number) Set the iTunes Genre ID (see --longhelp)\n" " --xID (string) Set the vendor-supplied iTunes xID (see " "--longhelp)\n" " --gapless (boolean) Set the gapless playback flag\n" " --contentRating (string*) Set tv/mpaa rating (see -rDNS-help)\n" "\n" "Deleting tags\n" " Set the value to \"\": --artist \"\" --stik \"\" --bpm \"\"\n" " To delete (all) artwork: --artwork REMOVE_ALL\n" " manually removal: --manualAtomRemove " "\"moov.udta.meta.ilst.ATOM\"\n" "\n" "More detailed iTunes help is available with AtomicParsley --longhelp\n" "Setting reverse DNS forms for iTunes files: see --reverseDNS-help\n" "Setting 3gp assets into 3GPP & derivative files: see --3gp-help\n" "Setting copyright notices for all files: see --ISO-help\n" "For file-level options & padding info: see --file-help\n" "Setting custom private tag extensions: see --uuid-help\n" "Setting ID3 tags onto mpeg-4 files: see --ID3-help\n" "\n" "----------------------------------------------------------------------"; // an expansive, verbose, unconstrained (about 112 char wide) detailing of // options static const char *longHelp_text = "AtomicParsley help page for setting iTunes-style metadata into MPEG-4 " "files. \n" " (3gp help available with AtomicParsley --3gp-help)\n" " (ISO copyright help available with AtomicParsley --ISO-help)\n" " (reverse DNS form help available with AtomicParsley " "--reverseDNS-help)\n" "Usage: AtomicParsley [mp4FILE]... [OPTION]... [ARGUMENT]... [ " "[OPTION2]...[ARGUMENT2]...] \n" "\n" "example: AtomicParsley /path/to.mp4 -e ~/Desktop/pix\n" "example: AtomicParsley /path/to.mp4 --podcastURL \"http://www.url.net\" " "--tracknum 45/356\n" "example: AtomicParsley /path/to.mp4 --copyright \"\342\204\227 \302\251 " "2006\"\n" "example: AtomicParsley /path/to.mp4 --year \"2006-07-27T14:00:43Z\" " "--purchaseDate timestamp\n" "example: AtomicParsley /path/to.mp4 --sortOrder artist \"Mighty Dub Cats, " "The\n" "--------------------------------------------------------------------------" "----------------------\n" " Extract any pictures in user data \"covr\" atoms to separate files. \n" " --extractPix , -E Extract to same folder " "(basename derived from file).\n" " --extractPixToPath , -e (/path/basename) Extract to specific path " "(numbers added to basename).\n" " example: --e " "~/Desktop/SomeText\n" " gives: " "SomeText_artwork_1.jpg SomeText_artwork_2.png\n" " Note: extension comes from " "embedded image file format\n" "--------------------------------------------------------------------------" "----------------------\n" " Tag setting options:\n" "\n" " --artist , -a (str) Set the artist tag: " "\"moov.udta.meta.ilst.\302\251ART.data\"\n" " --title , -s (str) Set the title tag: " "\"moov.udta.meta.ilst.\302\251nam.data\"\n" " --album , -b (str) Set the album tag: " "\"moov.udta.meta.ilst.\302\251alb.data\"\n" " --genre , -g (str) Set the genre tag: \"\302\251gen\" " "(custom) or \"gnre\" (standard).\n" " see the standard list with " "\"AtomicParsley --genre-list\"\n" " --tracknum , -k (num)[/tot] Set the track number (or track " "number & total tracks).\n" " --disk , -d (num)[/tot] Set the disk number (or disk " "number & total disks).\n" " --comment , -c (str) Set the comment tag: " "\"moov.udta.meta.ilst.\302\251cmt.data\"\n" " --year , -y (num|UTC) Set the year tag: " "\"moov.udta.meta.ilst.\302\251day.data\"\n" " set with UTC " "\"2006-09-11T09:00:00Z\" for Release Date\n" " --lyrics , -l (str) Set the lyrics tag: " "\"moov.udta.meta.ilst.\302\251lyr.data\"\n" " --lyricsFile , (/path) Set the lyrics tag to the content " "of a file\n" " --composer , -w (str) Set the composer tag: " "\"moov.udta.meta.ilst.\302\251wrt.data\"\n" " --copyright , -x (str) Set the copyright tag: " "\"moov.udta.meta.ilst.cprt.data\"\n" " --grouping , -G (str) Set the grouping tag: " "\"moov.udta.meta.ilst.\302\251grp.data\"\n" " --artwork , -A (/path) Set a piece of artwork (jpeg or " "png) on \"covr.data\"\n" " Note: multiple pieces are " "allowed with more --artwork args\n" " --bpm , -B (num) Set the tempo/bpm tag: " "\"moov.udta.meta.ilst.tmpo.data\"\n" " --albumArtist , -A (str) Set the album artist tag: " "\"moov.udta.meta.ilst.aART.data\"\n" " --compilation , -C (bool) Sets the \"cpil\" atom (true or " "false to delete the atom)\n" " --hdvideo , -V (bool) Sets the \"hdvd\" atom (true or " "false to delete the atom)\n" " --advisory , -y (1of3) Sets the iTunes lyrics advisory " "('remove', 'clean', 'explicit') \n" " --stik , -S (1of7) Sets the iTunes \"stik\" atom " "(--stik \"remove\" to delete) \n" " \"Movie\", \"Normal\", \"TV " "Show\" .... others: \n" " see the full list with " "\"AtomicParsley --stik-list\"\n" " or set in an integer value " "with --stik value=(num)\n" " Note: --stik Audiobook will change " "file extension to '.m4b'\n" " --description , -p (str) Sets the description on the " "\"desc\" atom\n" " --Rating , (str) Sets the Rating on the \"rate\" " "atom\n" " --longdesc , -j (str) Sets the long description on the " "\"ldes\" atom\n" " --storedesc , (str) Sets the iTunes store description " "on the \"sdes\" atom\n" " --TVNetwork , -n (str) Sets the TV Network name on the " "\"tvnn\" atom\n" " --TVShowName , -H (str) Sets the TV Show name on the " "\"tvsh\" atom\n" " --TVEpisode , -I (str) Sets the TV Episode on " "\"tven\":\"209\", but it is a string: \"209 Part 1\"\n" " --TVSeasonNum , -U (num) Sets the TV Season number on the " "\"tvsn\" atom\n" " --TVEpisodeNum , -N (num) Sets the TV Episode number on the " "\"tves\" atom\n" " --podcastFlag , -f (bool) Sets the podcast flag (values are " "\"true\" or \"false\")\n" " --category , -q (str) Sets the podcast category; " "typically a duplicate of its genre\n" " --keyword , -K (str) Sets the podcast keyword; invisible " "to MacOSX Spotlight\n" " --podcastURL , -L (URL) Set the podcast feed URL on the " "\"purl\" atom\n" " --podcastGUID , -J (URL) Set the episode's URL tag on the " "\"egid\" atom\n" " --purchaseDate , -D (UTC) Set Universal Coordinated Time of " "purchase on a \"purd\" atom\n" " (use \"timestamp\" to set UTC to " "now; can be akin to id3v2 TDTG tag)\n" " --encodingTool , (str) Set the name of the encoder on the " "\"\302\251too\" atom\n" " --encodedBy , (str) Set the name of the Person/company " "who encoded the file on the \"\302\251enc\" atom\n" " --apID , -Y (str) Set the name of the Account Name on " "the \"apID\" atom\n" " --cnID , (num) Set iTunes Catalog ID, used for " "combining SD and HD encodes in iTunes on the \"cnID\" atom\n" "\n" " To combine you must set \"hdvd\" " "atom on one file and must have same \"stik\" on both file\n" " Must not use \"stik\" of value Home " "Video(0), use Movie(9)\n" "\n" " iTunes Catalog numbers can be " "obtained by finding the item in the iTunes Store. Once item\n" " is found in the iTunes Store right " "click on picture of item and select copy link. Paste this link\n" " into a document or web browser to " "display the catalog number ID.\n" "\n" " An example link for the video " "Street Kings is:\n" " " "http://itunes.apple.com/WebObjects/MZStore.woa/wa/" "viewMovie?id=278743714&s=143441\n" " Here you can see the cnID is " "278743714\n" "\n" " Alternatively you can use iMDB " "numbers, however these will not match the iTunes catalog.\n" "\n" " --geID , (num) Set iTunes Genre ID. This does not " "necessarily have to match genre.\n" " See --genre-movie-id-list and " "--genre-tv-id-list\n" "\n" " --xID , (str) Set iTunes vendor-supplied xID, " "used to allow iTunes LPs and iTunes Extras to interact \n" " with other content in your " "iTunes Library\n" " --gapless , (bool) Sets the gapless playback flag for " "a track in a gapless album\n" " --sortOrder (type) (str) Sets the sort order string for that " "type of tag.\n" " (available types are: \"name\", " "\"artist\", \"albumartist\",\n" " \"album\", \"composer\", " "\"show\")\n" "\n" "NOTE: Except for artwork, only 1 of each tag is allowed; artwork allows " "multiple pieces.\n" "NOTE: Tags that carry text(str) have a limit of 255 utf8 characters;\n" "however lyrics and long descriptions have no limit.\n" "--------------------------------------------------------------------------" "----------------------\n" " To delete a single atom, set the tag to null (except artwork):\n" " --artist \"\" --lyrics \"\"\n" " --artwork REMOVE_ALL \n" " --metaEnema , -P Douches away every atom under " "\"moov.udta.meta.ilst\" \n" " --foobar2000Enema , -2 Eliminates foobar2000's " "non-compliant so-out-o-spec tagging scheme\n" " --manualAtomRemove \"some.atom.path\" where some.atom.path can be:\n" " keys to using manualAtomRemove:\n" " ilst.ATOM.data or ilst.ATOM target an iTunes-style metadata tag\n" " ATOM:lang=foo target an atom with this language " "setting; like 3gp assets\n" " ATOM.----.name:[foo] target a reverseDNS metadata tag; " "like iTunNORM\n" " Note: these atoms show up with 'AP " "-t' as: Atom \"----\" [foo]\n" " 'foo' is actually carried on the " "'name' atom\n" " ATOM[x] target an atom with an index other " "than 1; like trak[2]\n" " ATOM.uuid=hex-hex-hex-hex targt a uuid atom with the uuid of " "hex string representation\n" " examples:\n" " moov.udta.meta.ilst.----.name:[iTunNORM] " "moov.trak[3].cprt:lang=urd\n" " moov.trak[2].uuid=55534d54-21d2-4fce-bb88-695cfac9c740\n" "--------------------------------------------------------------------------" "----------------------\n" #if defined(__APPLE__) " Environmental Variables (affecting picture placement)\n" "\n" " set PIC_OPTIONS in your shell to set these flags; preferences are " "separated by colons (:)\n" "\n" " MaxDimensions=num (default: 0; unlimited); sets maximum pixel " "dimensions\n" " DPI=num (default: 72); sets dpi\n" " MaxKBytes=num (default: 0; unlimited); maximum kilobytes for file " "(jpeg only)\n" " AddBothPix=bool (default: false); add original & converted pic (for " "archival purposes)\n" " AllPixJPEG | AllPixPNG =bool (default: false); force conversion to a " "specific picture format\n" " SquareUp (include to square images to largest dimension, allows " "an [ugly] 160x1200->1200x1200)\n" " removeTempPix (include to delete temp pic files created when " "resizing images after tagging)\n" " ForceHeight=num (must also specify width, below) force image pixel " "height\n" " ForceWidth=num (must also specify height, above) force image pixel " "width\n" "\n" " Examples: (bash-style)\n" " export " "PIC_OPTIONS=\"MaxDimensions=400:DPI=72:MaxKBytes=100:AddBothPix=true:" "AllPixJPEG=true\"\n" " export PIC_OPTIONS=\"SquareUp:removeTempPix\"\n" " export PIC_OPTIONS=\"ForceHeight=999:ForceWidth=333:removeTempPix\"\n" "--------------------------------------------------------------------------" "----------------------\n" #endif ; static const char *fileLevelHelp_text = "AtomicParsley help page for general & file level options.\n" #if defined(_WIN32) #ifndef __CYGWIN__ " Note: you can change the input/output behavior to raw 8-bit utf8 if the " "program name\n" " is appended with \"-utf8\". AtomicParsley-utf8.exe will have " "problems with files/\n" " folders with unicode characters in given paths.\n" #else " Note: you can change the input/output behavior for MCDI functions to " "raw 8-bit utf8\n" " if the program name is appended with \"-utf8\".\n" #endif "\n" #endif "--------------------------------------------------------------------------" "----------------------\n" " Atom reading services:\n" "\n" " --test , -T Tests file to see if it is a valid " "MPEG-4 file.\n" " Prints out the hierarchical atom " "tree.\n" " -T 1 Supplemental track level info with " "\"-T 1\"\n" " -T +dates Track level with creation/modified " "dates\n" "\n" " --textdata , -t print user data text metadata relevant to " "brand (inc. # of any pics).\n" " -t + show supplemental info like free space, " "available padding, user data\n" " length & media data length\n" " -t 1 show all textual metadata (disregards " "brands, shows track copyright)\n" "\n" " --brands show the major & minor brands for the " "file & available tagging schemes\n" "\n" "--------------------------------------------------------------------------" "----------------------\n" " File services:\n" "\n" " --freefree [num] , Remove \"free\" atoms which only " "act as filler in the file\n" " ?(num)? - optional integer argument " "to delete 'free's to desired level\n" "\n" " NOTE 1: levels begin at level 1 aka " "file level.\n" " NOTE 2: Level 0 (which doesn't " "exist) deletes level 1 atoms that pre-\n" " cede 'moov' & don't serve " "as padding. Typically, such atoms\n" " are created by libmp4ff or " "libmp4v2 as a byproduct of tagging.\n" " NOTE 3: When padding falls below " "MIN_PAD (typically zero), a default\n" " amount of padding " "(typically 2048 bytes) will be added. To\n" " achieve absolutely 0 bytes " "'free' space with --freefree, set\n" " DEFAULT_PAD to 0 via the " "AP_PADDING mechanism (see below).\n" "\n" " --preventOptimizing Prevents reorganizing the file to " "have file metadata before media data.\n" " iTunes/Quicktime have so far " "*always* placed metadata first; many 3rd\n" " party utilities do not (preventing " "streaming to the web, AirTunes, iTV).\n" " Used in conjunction with " "--overWrite, files with metadata at the end\n" " (most ffmpeg produced files) can " "have their tags rapidly updated without\n" " requiring a full rewrite. Note: " "this does not un-optimize a file.\n" " Note: this option will be canceled " "out if used with the --freefree option\n" "\n" " --metaDump Dumps out 'moov.udta' metadata out " "to a new file next to original\n" " (for diagnostic purposes, " "please remove artwork before sending)\n" " --output , -o (/path) Specify the filename of tempfile " "(voids overWrite)\n" " --overWrite , -W Writes to temp file; deletes " "original, renames temp to original\n" " If possible, padding will be used " "to update without a full rewrite.\n" "\n" #if defined(_WIN32) " --preserveTime Will overwrite the original file in " "place (--overWrite forced),\n" " but will also keep the original " "file's timestamps intact.\n" "\n" #endif " --DeepScan Parse areas of the file that are " "normally skipped (must be the 3rd arg)\n" " --iPod-uuid (num) Place the ipod-required uuid for " "higher resolution avc video files\n" " Currently, the only number used is " "1200 - the maximum number of macro-\n" " blocks allowed by the higher " "resolution iPod setting.\n" " NOTE: this requires the " "\"--DeepScan\" option as the 3rd cli argument\n" " NOTE2: only works on the first avc " "video track, not all avc tracks\n" "\n" "Examples: \n" " --freefree 0 (deletes all top-level non-padding atoms preceding " "'mooov') \n" " --freefree 1 (deletes all non-padding atoms at the top most " "level) \n" " --output ~/Desktop/newfile.mp4\n" " AP /path/to/file.m4v --DeepScan --iPod-uuid 1200\n" "--------------------------------------------------------------------------" "----------------------\n" " Padding & 'free' atoms:\n" "\n" " A special type of atom called a 'free' atom is used for padding (all " "'free' atoms contain NULL space).\n" " When changes need to occur, these 'free' atom are used. They grows or " "shink, but the relative locations\n" " of certain other atoms (stco/mdat) remain the same. If there is no " "'free' space, a full rewrite will occur.\n" " The locations of 'free' atom(s) that AP can use as padding must be " "follow 'moov.udta' & come before 'mdat'.\n" " A 'free' preceding 'moov' or following 'mdat' won't be used as padding " "for example. \n" "\n" " Set the shell variable AP_PADDING with these values, separated by " "colons to alter padding behavior:\n" "\n" " DEFAULT_PADDING= - the amount of padding added if the minimum padding " "is non-existant in the file\n" " default = 2048\n" " MIN_PAD= - the minimum padding present before more padding " "will be added\n" " default = 0\n" " MAX_PAD= - the maximum allowable padding; excess padding will " "be eliminated\n" " default = 5000\n" "\n" " If you use --freefree to eliminate 'free' atoms from the file, the " "DEFAULT_PADDING amount will still be\n" " added to any newly written files. Set DEFAULT_PADDING=0 to prevent any " "'free' padding added at rewrite.\n" " You can set MIN_PAD to be assured that at least that amount of padding " "will be present - similarly,\n" " MAX_PAD limits any excessive amount of padding. All 3 options will in " "all likelyhood produce a full\n" " rewrite of the original file. Another case where a full rewrite will " "occur is when the original file\n" " is not optimized and has 'mdat' preceding 'moov'.\n" "\n" #if defined(_WIN32) && !defined(__CYGWIN__) "Examples:\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=0\" or c:> SET " "AP_PADDING=\"DEFAULT_PAD=3128\"\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=5128:MIN_PAD=200:MAX_PAD=6049\"\n" #else "Examples (bash style):\n" " $ export AP_PADDING=\"DEFAULT_PAD=0\" or $ export " "AP_PADDING=\"DEFAULT_PAD=3128\"\n" " $ export AP_PADDING=\"DEFAULT_PAD=5128:MIN_PAD=200:MAX_PAD=6049\"\n" #endif "\n" "Note: while AtomicParsley is still in the beta stage, the original file " "will always remain untouched - \n" " unless given the --overWrite flag when if possible, utilizing " "available padding to update tags\n" " will be tried (falling back to a full rewrite if changes are " "greater than the found padding).\n" "--------------------------------------------------------------------------" "--------------------------\n" " iTunes 7 & Gapless playback:\n" "\n" " iTunes 7 adds NULL space at the ends of files (filled with zeroes). It " "is possble this is how iTunes\n" " implements gapless playback - perhaps not. In any event, with " "AtomicParsley you can choose to preserve\n" " that NULL space, or you can eliminate its presence (typically around " "2,000 bytes). The default behavior\n" " is to preserve it - if it is present at all. You can choose to eliminate " "it by setting the environ-\n" " mental preference for AP_PADDING to have DEFAULT_PAD=0\n" "\n" #if defined(_WIN32) && !defined(__CYGWIN__) "Example:\n" " c:> SET AP_PADDING=\"DEFAULT_PAD=0\"\n" #else "Example (bash style):\n" " $ export AP_PADDING=\"DEFAULT_PAD=0\"\n" #endif "--------------------------------------------------------------------------" "--------------------------\n"; // detailed options for 3gp branded files static const char *_3gpHelp_text = "AtomicParsley 3gp help page for setting 3GPP-style metadata.\n" "--------------------------------------------------------------------------" "--------------------------\n" " 3GPP text tags can be encoded in either UTF-8 (default input encoding) " "or UTF-16 (converted from UTF-8)\n" " Many 3GPP text tags can be set for a desired language by a " "3-letter-lowercase code (default is \"eng\").\n" " For tags that support the language attribute (all except year), more " "than one tag of the same name\n" " (3 titles for example) differing in the language code is supported.\n" "\n" " iTunes-style metadata is not supported by the 3GPP TS 26.244 version " "6.4.0 Release 6 specification.\n" " 3GPP asset tags can be set at movie level or track level & are set in a " "different hierarchy: moov.udta \n" " if at movie level (versus iTunes moov.udta.meta.ilst). Other 3rd party " "utilities may allow setting\n" " iTunes-style metadata in 3gp files. When a 3gp file is detected (file " "extension doesn't matter), only\n" " 3gp spec-compliant metadata will be read & written.\n" "\n" " Note1: there are a number of different 'brands' that 3GPP files come " "marked as. Some will not be \n" " supported by AtomicParsley due simply to them being unknown and " "untested. You can compile your\n" " own AtomicParsley to evaluate it by adding the hex code into the " "source of APar_IdentifyBrand.\n" "\n" " Note2: There are slight accuracy discrepancies in location's fixed " "point decimals set and retrieved.\n" "\n" " Note3: QuickTime Player can see a limited subset of these tags, but " "only in 1 language & there seems to\n" " be an issue with not all unicode text displaying properly. This " "is an issue withing QuickTime -\n" " the exact same text (in utf8) displays properly in an MPEG-4 " "file. Some languages can also display\n" " more glyphs than others.\n" "\n" "--------------------------------------------------------------------------" "--------------------------\n" " Tag setting options (default user data area is movie level; default lang " "is 'eng'; default encoding is UTF8):\n" " required arguments are in (parentheses); optional arguments are in " "[brackets]\n" "\n" " --3gp-title (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp media title tag\n" " --3gp-author (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp author of the media tag\n" " --3gp-performer (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp performer or artist tag\n" " --3gp-genre (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp genre asset tag\n" " --3gp-description (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp description or caption tag\n" " --3gp-copyright (str) [lang=3str] [UTF16] [area] ......... " "Set a 3gp copyright notice tag*\n" "\n" " --3gp-album (str) [lang=3str] [UTF16] [trknum=int] [area] " "Set a 3gp album tag (& opt. tracknum)\n" " --3gp-year (int) [area] ........................... Set a " "3gp recording year tag (4 digit only)\n" "\n" " --3gp-rating (str) [entity=4str] [criteria=4str] " "[lang=3str] [UTF16] [area] Rating tag\n" " --3gp-classification (str) [entity=4str] [index=int] " "[lang=3str] [UTF16] [area] Classification\n" "\n" " --3gp-keyword (str) [lang=3str] [UTF16] [area] Format of " "str: 'keywords=word1,word2,word3,word4'\n" "\n" " --3gp-location (str) [lang=3str] [UTF16] [area] Set a 3gp " "location tag (default: Central Park)\n" " [longitude=fxd.pt] [latitude=fxd.pt] " "[altitude=fxd.pt]\n" " [role=str] [body=str] [notes=str]\n" " fxd.pt values are decimal coordinates " "(55.01209, 179.25W, 63)\n" " 'role=' values: 'shooting location', " "'real location', 'fictional location'\n" " a negative value in coordinates " "will be seen as a cli flag\n" " append 'S', 'W' or 'B': lat=55S, " "long=90.23W, alt=90.25B\n" "\n" " [area] can be \"movie\", \"track\" or \"track=num\" where 'num' is the " "number of the track. If not specificied,\n" " assets will be placed at movie level. The \"track\" option sets the " "asset across all available tracks\n" "\n" "Note1: '4str' = a 4 letter string like \"PG13\"; 3str is a 3 letter " "string like \"eng\"; int is an integer\n" "Note2: List all languages for '3str' with \"AtomicParsley --language-list " "(unknown languages become \"und\")\n" "*Note3: The 3gp copyright asset can potentially be altered by using the " "--ISO-copyright setting.\n" "--------------------------------------------------------------------------" "--------------------------\n" "Usage: AtomicParsley [3gpFILE] --option [argument] [optional_arguments] " "[ --option2 [argument2]...] \n" "\n" "example: AtomicParsley /path/to.3gp -t \n" "example: AtomicParsley /path/to.3gp -T 1 \n" "example: Atomicparsley /path/to.3gp --3gp-performer \"Enjoy Yourself\" " "lang=pol UTF16\n" "example: Atomicparsley /path/to.3gp --3gp-year 2006 --3gp-album \"White " "Label\" track=8 lang=fra\n" "example: Atomicparsley /path/to.3gp --3gp-album \"Cow Cod Soup For " "Everyone\" track=10 lang=car\n" "\n" "example: Atomicparsley /path/to.3gp --3gp-classification \"Poor Sport\" " "entity=\"PTA \" index=12 UTF16\n" "example: Atomicparsley /path/to.3gp --3gp-keyword " "keywords=\"foo1,foo2,foo 3\" UTF16 --3gp-keyword \"\"\n" "example: Atomicparsley /path/to.3gp --3gp-location 'Bethesda Terrace' " "latitude=40.77 longitude=73.98W \n" " altitude=4.3B " "role='real' body=Earth notes='Underground'\n" "\n" "example: Atomicparsley /path/to.3gp --3gp-title \"I see London.\" " "--3gp-title \"Veo Madrid.\" lang=spa \n" " --3gp-title \"Widze Warsawa.\" " "lang=pol\n" "\n"; static const char *ISOHelp_text = "AtomicParsley help page for setting ISO copyright notices at movie & " "track level.\n" "--------------------------------------------------------------------------" "--------------------------\n" " The ISO specification allows for setting copyright in a number of " "places. This copyright atom is\n" " independant of the iTunes-style --copyright tag that can be set. This " "ISO tag is identical to the\n" " 3GP-style copyright. In fact, using --ISO-copyright can potentially " "overwrite the 3gp copyright\n" " asset if set at movie level & given the same language to set the " "copyright on. This copyright\n" " notice is the only metadata tag defined by the reference ISO 14496-12 " "specification.\n" "\n" " ISO copyright notices can be set at movie level, track level for a " "single track, or for all tracks.\n" " Multiple copyright notices are allowed, but they must differ in the " "language setting. To see avail-\n" " able languages use \"AtomicParsley --language-list\". Notices can be " "set in utf8 or utf16.\n" "\n" " --ISO-copyright (str) [movie|track|track=#] [lang=3str] [UTF16] " "Set a copyright notice\n" " # in 'track=#' " "denotes the target track\n" " 3str is the 3 " "letter ISO-639-2 language.\n" " Brackets [] " "show optional parameters.\n" " Defaults are: " "movie level, 'eng' in utf8.\n" "\n" "example: AtomicParsley /path/file.mp4 -t 1 Note: the only way to see " "all contents is with -t 1 \n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\"\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" movie\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" track=2 " "lang=urd\n" "example: AtomicParsley /path/file.mp4 --ISO-copyright \"Sample\" track " "UTF16\n" "example: AP --ISO-copyright \"Example\" track --ISO-copyright \"Por " "Exemplo\" track=2 lang=spa UTF16\n" "\n" "Note: to remove the copyright, set the string to \"\" - the track and " "language must match the target.\n" "example: --ISO-copyright \"\" track --ISO-copyright \"\" track=2 " "lang=spa\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional parameters " "& may have defaults.\n"; static const char *uuidHelp_text = "AtomicParsley help page for setting uuid user extension metadata tags.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Setting a user-defined 'uuid' private extention tags will appear in " "\"moov.udta.meta\"). These will\n" " only be read by AtomicParsley & can be set irrespective of file " "branding. The form of uuid that AP\n" " is a v5 uuid generated from a sha1 hash of an atom name in an " "'AtomicParsley.sf.net' namespace.\n" "\n" " The uuid form is in some Sony & Compressor files, but of version 4 " "(random/pseudo-random). An example\n" " uuid of 'cprt' in the 'AtomicParsley.sf.net' namespace is: " "\"4bd39a57-e2c8-5655-a4fb-7a19620ef151\".\n" " 'cprt' in the same namespace will always create that uuid; uuid atoms " "will only print out if the\n" " uuid generated is the same as discovered. Sony uuids don't for example " "show up with AP -t.\n" "\n" " --information , -i (str) Set an information tag on uuid atom " "name\"\251inf\"\n" " --url , -u (URL) Set a URL tag on uuid atom name " "\"\302\251url\"\n" " --tagtime , timestamp Set the Coordinated Univeral Time " "of tagging on \"tdtg\"\n" "\n" " Define & set an arbitrary atom with a text data or embed a file:\n" " --meta-uuid There are two forms: 1 for text & 1 for file " "operations\n" " setting text form:\n" " --meta-uuid (atom) \"text\" (str) \"atom\" = 4 " "character atom name of your choice\n" " str is whatever text " "you want to set\n" " file embedding form:\n" " --meta-uuid (atom) \"file\" (/path) [description=\"foo\"] " "[mime-type=\"foo/moof\"]\n" " \"atom\" = 4 " "character atom name of your choice\n" " /path = path to the " "file that will be embedded*\n" " description = " "optional description of the file\n" " default is " "\"[none]\"\n" " mime-type = optional " "mime type for the file\n" " default is " "\"none\"\n" " Note: no " "auto-disocevery of mime type\n" " if " "you know/want it: supply it.\n" " *Note: a file extension " "(/path/file.ext) is required\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional arguments " "& may have defaults.\n" "\n" "Examples: \n" " --tagtime timestamp --information \"[psst]I see metadata\" --url " "http://www.bumperdumper.com\n" " --meta-uuid tagr text \"Johnny Appleseed\" --meta-uuid \302\251sft text " "\"OpenShiiva encoded.\"\n" " --meta-uuid scan file /usr/pix/scans.zip\n" " --meta-uuid 1040 file ../../2006_taxes.pdf description=\"Fooled 'The " "Man' yet again.\"\n" "can be removed with:\n" " --tagtime \"\" --information \"\" --url \" \" --meta-uuid scan file " "\n" " --manualAtomRemove " "\"moov.udta.meta.uuid=672c98cd-f11f-51fd-adec-b0ee7b4d215f\" \\\n" " --manualAtomRemove " "\"moov.udta.meta.uuid=1fed6656-d911-5385-9cb2-cb2c100f06e7\"\n" "Remove the Sony uuid atoms with:\n" " --manualAtomRemove " "moov.trak[1].uuid=55534d54-21d2-4fce-bb88-695cfac9c740 \\\n" " --manualAtomRemove " "moov.trak[2].uuid=55534d54-21d2-4fce-bb88-695cfac9c740 \\\n" " --manualAtomRemove uuid=50524f46-21d2-4fce-bb88-695cfac9c740\n" "\n" "Viewing the contents of uuid atoms:\n" " -t or --textdata Shows the uuid atoms (both text & file) that " "AP sets:\n" " Example output:\n" " Atom uuid=ec0f...d7 (AP uuid for \"scan\") contains: FILE.zip; " "description=[none]\n" " Atom uuid=672c...5f (AP uuid for \"tagr\") contains: Johnny " "Appleseed\n" "\n" "Extracting an embedded file in a uuid atom:\n" " --extract1uuid (atom) Extract file embedded within " "uuid=atom into same folder\n" " (file will be named with suffix " "shown in --textdata)\n" " --extract-uuids [/path] Extract all files in uuid atoms " "under the moov.udta.meta\n" " hierarchy. If no /path is given, " "files will be extracted\n" " to the same folder as the " "originating file.\n" "\n" " Examples:\n" " --extract1uuid scan\n" " ... Extracted uuid=scan attachment to file: " "/some/path/FILE_scan_uuid.zip\n" " --extract-uuids ~/Desktop/plops\n" " ... Extracted uuid=pass attachment to file: " "/Users/me/Desktop/plops_pass_uuid.pdf\n" " ... Extracted uuid=site attachment to file: " "/Users/me/Desktop/plops_site_uuid.html\n" "\n" "--------------------------------------------------------------------------" "----------------------\n"; static const char *rDNSHelp_text = "AtomicParsley help page for setting reverse domain '----' metadata " "atoms.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Please note that the reverse DNS format supported here is not " "feature complete.\n" "\n" " Another style of metadata that iTunes uses is called the reverse DNS " "format. For all known tags,\n" " iTunes offers no user-accessible exposure to these tags or their " "contents. This reverse DNS form has\n" " a differnt form than other iTunes tags that have a atom name that " "describes the content of 'data'\n" " atom it contains. In the reverseDNS format, the parent to the structure " "called the '----' atom, with\n" " children atoms that describe & contain the metadata carried. The 'mean' " "child contains the reverse\n" " domain itself ('com.apple.iTunes') & the 'name' child contains the " "descriptor ('iTunNORM'). A 'data'\n" " atom follows that actually contains the contents of the tag.\n" "\n" " --contentRating (rating) Set the US TV/motion picture media " "content rating\n" " for available ratings use " "\"AtomicParsley --ratings-list\n" " --rDNSatom (str) name=(name_str) domain=(reverse_domain) " "Manually set a reverseDNS atom.\n" "\n" " To set the form manually, 3 things are required: a domain, a name, and " "the desired text.\n" " Note: multiple 'data' atoms are supported, but not in the " "com.apple.iTunes domain\n" " Examples:\n" " --contentRating \"NC-17\" --contentRating \"TV-Y7\"\n" " --rDNSatom \"mpaa|PG-13|300|\" name=iTunEXTC domain=com.apple.iTunes\n" " --contentRating \"\"\n" " --rDNSatom \"\" name=iTunEXTC domain=com.apple.iTunes\n" " --rDNSatom \"try1\" name=EVAL domain=org.fsf --rDNSatom \"try 2\" " "name=EVAL domain=org.fsf\n" " --rDNSatom \"\" name=EVAL domain=org.fsf\n" "--------------------------------------------------------------------------" "--------------------------\n"; static const char *ID3Help_text = "AtomicParsley help page for ID32 atoms with ID3 tags.\n" "--------------------------------------------------------------------------" "--------------------------\n" " ** Please note: ID3 tag support is not feature complete & is in an " "alpha state. **\n" "--------------------------------------------------------------------------" "--------------------------\n" " ID3 tags are the tagging scheme used by mp3 files (where they are found " "typically at the start of the\n" " file). In mpeg-4 files, ID3 version 2 tags are located in specific " "hierarchies at certain levels, at\n" " file/movie/track level. The way that ID3 tags are carried on mpeg-4 " "files (carried by 'ID32' atoms)\n" " was added in early 2006, but the ID3 tagging 'informal standard' was " "last updated (to v2.4) in 2000.\n" " With few exceptions, ID3 tags in mpeg-4 files exist identically to their " "mp3 counterparts.\n" "\n" " The ID3 parlance, a frame contains an piece of metadata. A frame (like " "COMM for comment, or TIT1 for\n" " title) contains the information, while the tag contains all the frames " "collectively. The 'informal\n" " standard' for ID3 allows multiple langauges for frames like COMM " "(comment) & USLT (lyrics). In mpeg-4\n" " this language setting is removed from the ID3 domain and exists in the " "mpeg-4 domain. That means that\n" " when an english and a spanish comment are set, 2 separate ID32 atoms are " "created, each with a tag & 1\n" " frame as in this example:\n" " --ID3Tag COMM \"Primary\" --desc=AAA --ID3Tag COMM \"El Segundo\" " "UTF16LE lang=spa --desc=AAA\n" " See available frames with \"AtomicParsley --ID3frames-list\"\n" " See avilable imagetypes with \"AtomicParsley --imagetype-list\"\n" "\n" " AtomicParsley writes ID3 version 2.4.0 tags *only*. There is no " "up-converting from older versions.\n" " Defaults are:\n" " default to movie level (moov.meta.ID32); other options are [ \"root\", " "\"track=(num)\" ] (see WARNING)\n" " UTF-8 text encoding when optional; other options are [ \"LATIN1\", " "\"UTF16BE\", \"UTF16LE\" ]\n" " frames that require descriptions have a default of \"\"\n" " for frames requiring a language setting, the ID32 language is used " "(currently defaulting to 'eng')\n" " frames that require descriptions have a default of \"\"\n" " image type defaults to 0x00 or Other; for image type 0x01, 32x32 png " "is enforced (switching to 0x02)\n" " setting the image mimetype is generally not required as the file is " "tested, but can be overridden\n" " zlib compression off\n" "\n" " WARNING:\n" " Quicktime Player (up to v7.1.3 at least) will freeze opeing a file " "with ID32 tags at movie level.\n" " Specifically, the parent atom, 'meta' is the source of the issue. " "You can set the tags at file or\n" " track level which avoids the problem, but the default is movie " "level. iTunes is unaffected.\n" "--------------------------------------------------------------------------" "--------------------------\n" " Current limitations:\n" " - syncsafe integers are used as indicated by the id3 \"informal " "standard\". usage/reading of\n" " nonstandard ordinary unsigned integers (uint32_t) is not/will not be " "implemented.\n" " - externally referenced images (using mimetype '-->') are prohibited " "by the ID32 specification.\n" " - the ID32 atom is only supported in a non-referenced context\n" " - probably a raft of other limitations that my brain lost along the " "way...\n" "--------------------------------------------------------------------------" "--------------------------\n" " Usage:\n" " --ID3Tag (frameID or alias) (str) [desc=(str)] [mimetype=(str)] " "[imagetype=(str or hex)] [...]\n" "\n" " ... represents other arguments:\n" " [compressed] zlib compress the frame\n" " [UTF16BE, UTF16LE, LATIN1] alternative text encodings for frames that " "support different encodings\n" "\n" "Note: (foo) denotes required arguments; [foo] denotes optional " "parameters\n" "\n" " Examples:\n" " --ID3Tag APIC /path/to/img.ext\n" " --ID3Tag APIC /path/to/img.ext desc=\"something to say\" imagetype=0x08 " "UTF16LE compressed\n" " --ID3Tag composer \"I, Claudius\" --ID3Tag TPUB \"Seneca the Roman\" " "--ID3Tag TMOO Imperial\n" " --ID3Tag UFID look@me.org uniqueID=randomUUIDstamp\n" "\n" " Extracting embedded images in APIC frames:\n" " --ID3Tag APIC extract\n" " images are extracted into the same directory as the source mpeg-4 " "file\n" "\n" #if defined(__APPLE__) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI disk4\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. If the target\n" " disk is not found or is not an audio CD, a scan of all devices " "will occur. If an Audio CD\n" " is present, the scan should yield what should be entered after " "'MCDI':\n" " % AP file --ID3Tag MCDI disk3\n" " % No cd present in drive at disk3\n" " % Device 'disk4' contains cd media\n" " % Good news, device 'disk4' is an Audio CD and can be used for " "'MCDI' setting\n" #elif defined(__linux__) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI /dev/hdc\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. An Audio CD\n" " must be mounted & present.\n" #elif defined(_WIN32) " Setting MCDI (Music CD Identifier):\n" " --ID3Tag MCDI D\n" " Information to create this frame is taken directly off an Audio " "CD's TOC. The letter after\n" " \"MCDI\" is the letter of the drive where the CD is present.\n" #endif ; void ExtractPaddingPrefs(char *env_padding_prefs) { pad_prefs.default_padding_size = DEFAULT_PADDING_LENGTH; pad_prefs.minimum_required_padding_size = MINIMUM_REQUIRED_PADDING_LENGTH; pad_prefs.maximum_present_padding_size = MAXIMUM_REQUIRED_PADDING_LENGTH; if (env_padding_prefs != NULL) { if (env_padding_prefs[0] == 0x22 || env_padding_prefs[0] == 0x27) env_padding_prefs++; } char *env_pad_prefs_ptr = env_padding_prefs; while (env_pad_prefs_ptr != NULL) { env_pad_prefs_ptr = strsep(&env_padding_prefs, ":"); if (env_pad_prefs_ptr == NULL) break; if (strncmp(env_pad_prefs_ptr, "DEFAULT_PAD=", 12) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.default_padding_size); } if (strncmp(env_pad_prefs_ptr, "MIN_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.minimum_required_padding_size); } if (strncmp(env_pad_prefs_ptr, "MAX_PAD=", 8) == 0) { strsep(&env_pad_prefs_ptr, "="); sscanf(env_pad_prefs_ptr, "%" SCNu32, &pad_prefs.maximum_present_padding_size); } } // fprintf(stdout, "Def %" PRIu32 "; Min %" PRIu32 "; Max %" PRIu32 "\n", // pad_prefs.default_padding_size, pad_prefs.minimum_required_padding_size, // pad_prefs.maximum_present_padding_size); return; } void GetBasePath(const char *filepath, char *&basepath) { // with a myriad of m4a, m4p, mp4, whatever else comes up... it might just be // easiest to strip off the end. int split_here = 0; for (int i = strlen(filepath); i >= 0; i--) { const char *this_char = &filepath[i]; if (*this_char == '.') { split_here = i; break; } } memcpy(basepath, filepath, (size_t)split_here); return; } void find_optional_args(char *argv[], int start_optindargs, uint16_t &packed_lang, bool &asUTF16, uint8_t &udta_container, uint8_t &trk_idx, int max_optargs) { asUTF16 = false; packed_lang = 5575; // und = 0x55C4 = 21956, but QTPlayer doesn't like und // //eng = 0x15C7 = 5575 for (int i = 0; i <= max_optargs - 1; i++) { if (argv[start_optindargs + i] && start_optindargs + i <= total_args) { if (strncmp(argv[start_optindargs + i], "lang=", 5) == 0) { if (!MatchLanguageCode(argv[start_optindargs + i] + 5)) { packed_lang = PackLanguage("und", 0); } else { packed_lang = PackLanguage(argv[start_optindargs + i], 5); } } else if (strcmp(argv[start_optindargs + i], "UTF16") == 0) { asUTF16 = true; } else if (strcmp(argv[optind + i], "movie") == 0) { udta_container = MOVIE_LEVEL_ATOM; } else if (strncmp(argv[optind + i], "track=", 6) == 0) { char *track_index_str = argv[optind + i]; strsep(&track_index_str, "="); trk_idx = strtoul(track_index_str, NULL, 10); udta_container = SINGLE_TRACK_ATOM; } else if (strcmp(argv[optind + i], "track") == 0) { udta_container = ALL_TRACKS_ATOM; } if (*argv[start_optindargs + i] == '-') { break; // we've hit another cli argument } } } return; } void scan_ID3_optargs(char *argv[], int start_optargs, const char *&target_lang, uint16_t &packed_lang, uint8_t &char_encoding, char *meta_container, bool &multistring) { packed_lang = 5575; // default ID32 lang is 'eng' uint16_t i = 0; while (argv[start_optargs + i] != NULL) { if (argv[start_optargs + i] && start_optargs + i <= total_args) { if (strncmp(argv[start_optargs + i], "lang=", 5) == 0) { if (!MatchLanguageCode(argv[start_optargs + i] + 5)) { packed_lang = PackLanguage("und", 0); target_lang = "und"; } else { packed_lang = PackLanguage(argv[start_optargs + i], 5); target_lang = argv[start_optargs + i] + 5; } } else if (strcmp(argv[start_optargs + i], "UTF16LE") == 0) { char_encoding = TE_UTF16LE_WITH_BOM; } else if (strcmp(argv[start_optargs + i], "UTF16BE") == 0) { char_encoding = TE_UTF16BE_NO_BOM; } else if (strcmp(argv[start_optargs + i], "LATIN1") == 0) { char_encoding = TE_LATIN1; } else if (strcmp(argv[optind + i], "root") == 0) { *meta_container = 0 - FILE_LEVEL_ATOM; } else if (strncmp(argv[optind + i], "track=", 6) == 0) { char *track_index_str = argv[optind + i]; strsep(&track_index_str, "="); *meta_container = strtoul(track_index_str, NULL, 10); } } if (*argv[start_optargs + i] == '-') { break; // we've hit another cli argument or deleting some frame } i++; } return; } const char * find_ID3_optarg(char *argv[], int start_optargs, const char *arg_string) { const char *ret_val = ""; uint16_t i = 0; uint8_t arg_prefix_len = strlen(arg_string); while (argv[start_optargs + i] != NULL) { if (argv[start_optargs + i] && start_optargs + i <= total_args) { if (strcmp(arg_string, "compressed") == 0 && strcmp(argv[start_optargs + i], "compressed") == 0) { return "1"; } // if (strcmp(arg_string, "text++") == 0 && strcmp(argv[start_optargs + // i], "text++") == 0) { // return "1"; //} if (strncmp(argv[start_optargs + i], arg_string, arg_prefix_len) == 0) { ret_val = argv[start_optargs + i] + arg_prefix_len; break; } } if (*argv[start_optargs + i] == '-') { break; // we've hit another cli argument or deleting some frame } i++; } return ret_val; } //*********************************************** static void show_short_help(void) { printf("%s\n", shortHelp_text); ShowVersionInfo(); printf("\nSubmit bug fixes to https://github.com/wez/atomicparsley\n"); } int real_main(int argc, char *argv[]) { if (argc == 1) { show_short_help(); exit(0); } else if (argc == 2 && ((strcmp(argv[1], "-v") == 0) || (strcmp(argv[1], "-version") == 0) || (strcmp(argv[1], "--version") == 0))) { ShowVersionInfo(); exit(0); } else if (argc == 2) { if ((strcmp(argv[1], "-help") == 0) || (strcmp(argv[1], "--help") == 0) || (strcmp(argv[1], "-h") == 0)) { show_short_help(); exit(0); } else if ((strcmp(argv[1], "--longhelp") == 0) || (strcmp(argv[1], "-longhelp") == 0) || (strcmp(argv[1], "-Lh") == 0)) { #if defined(_WIN32) && !defined(__CYGWIN__) if (UnicodeOutputStatus == WIN32_UTF16) { // convert the helptext to utf16 // to preserve \251 characters int help_len = strlen(longHelp_text) + 1; wchar_t *Lhelp_text = (wchar_t *)malloc(sizeof(wchar_t) * help_len); wmemset(Lhelp_text, 0, help_len); UTF8ToUTF16LE((unsigned char *)Lhelp_text, 2 * help_len, (unsigned char *)longHelp_text, help_len); APar_unicode_win32Printout(Lhelp_text, (char *)longHelp_text); free(Lhelp_text); } else #endif { fprintf(stdout, "%s", longHelp_text); } exit(0); } else if ((strcmp(argv[1], "--3gp-help") == 0) || (strcmp(argv[1], "-3gp-help") == 0) || (strcmp(argv[1], "--3gp-h") == 0)) { fprintf(stdout, "%s\n", _3gpHelp_text); exit(0); } else if ((strcmp(argv[1], "--ISO-help") == 0) || (strcmp(argv[1], "--iso-help") == 0) || (strcmp(argv[1], "-Ih") == 0)) { fprintf(stdout, "%s\n", ISOHelp_text); exit(0); } else if ((strcmp(argv[1], "--file-help") == 0) || (strcmp(argv[1], "-file-help") == 0) || (strcmp(argv[1], "-fh") == 0)) { fprintf(stdout, "%s\n", fileLevelHelp_text); exit(0); } else if ((strcmp(argv[1], "--uuid-help") == 0) || (strcmp(argv[1], "-uuid-help") == 0) || (strcmp(argv[1], "-uh") == 0)) { fprintf(stdout, "%s\n", uuidHelp_text); exit(0); } else if ((strcmp(argv[1], "--reverseDNS-help") == 0) || (strcmp(argv[1], "-rDNS-help") == 0) || (strcmp(argv[1], "-rh") == 0)) { fprintf(stdout, "%s\n", rDNSHelp_text); exit(0); } else if ((strcmp(argv[1], "--ID3-help") == 0) || (strcmp(argv[1], "-ID3-help") == 0) || (strcmp(argv[1], "-ID3h") == 0)) { fprintf(stdout, "%s\n", ID3Help_text); exit(0); } else if (strcmp(argv[1], "--genre-list") == 0) { ListGenresValues(); exit(0); } else if (strcmp(argv[1], "--stik-list") == 0) { ListStikValues(); exit(0); } else if (strcmp(argv[1], "--language-list") == 0 || strcmp(argv[1], "--languages-list") == 0 || strcmp(argv[1], "--list-language") == 0 || strcmp(argv[1], "--list-languages") == 0 || strcmp(argv[1], "-ll") == 0) { ListLanguageCodes(); exit(0); } else if (strcmp(argv[1], "--ratings-list") == 0) { ListMediaRatings(); exit(0); } else if (strcmp(argv[1], "--genre-movie-id-list") == 0) { ListMovieGenreIDValues(); exit(0); } else if (strcmp(argv[1], "--genre-tv-id-list") == 0) { ListTVGenreIDValues(); exit(0); } else if (strcmp(argv[1], "--ID3frames-list") == 0) { ListID3FrameIDstrings(); exit(0); } else if (strcmp(argv[1], "--imagetype-list") == 0) { List_imagtype_strings(); exit(0); } } if (argc == 3 && (strcmp(argv[2], "--brands") == 0 || strcmp(argv[2], "-brands") == 0)) { APar_ExtractBrands(argv[1]); exit(0); } int extr = 99; total_args = argc; char *ISObasemediafile = argv[1]; TestFileExistence(ISObasemediafile, true); char *padding_options = getenv("AP_PADDING"); ExtractPaddingPrefs(padding_options); // it would probably be better to test output_file if provided & if // --overWrite was provided.... probably only of use on Windows - and I'm not // on it. if (strlen(ISObasemediafile) + 11 > MAXPATHLEN) { fprintf(stderr, "%c %s", '\a', "AtomicParsley error: filename/filepath was too long.\n"); exit(1); } if (argc > 3 && strcmp(argv[2], "--DeepScan") == 0) { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile, true); } while (1) { static struct option long_options[] = { {"help", 0, NULL, OPT_HELP}, {"test", optional_argument, NULL, OPT_TEST}, {"textdata", optional_argument, NULL, OPT_ShowTextData}, {"extractPix", 0, NULL, OPT_ExtractPix}, {"extractPixToPath", required_argument, NULL, OPT_ExtractPixToPath}, {"artist", required_argument, NULL, Meta_artist}, {"artDirector", required_argument, NULL, Meta_artDirector}, {"arranger", required_argument, NULL, Meta_arranger}, {"author", required_argument, NULL, Meta_author}, {"conductor", required_argument, NULL, Meta_conductor}, {"director", required_argument, NULL, Meta_director}, {"originalArtist", required_argument, NULL, Meta_originalArtist}, {"producer", required_argument, NULL, Meta_producer}, // { "performer", required_argument, NULL, Meta_performer // }, {"soundEngineer", required_argument, NULL, Meta_soundEngineer}, {"soloist", required_argument, NULL, Meta_soloist}, {"executiveProducer", required_argument, NULL, Meta_executiveProducer}, {"title", required_argument, NULL, Meta_songtitle}, {"subtitle", required_argument, NULL, Meta_subtitle}, {"album", required_argument, NULL, Meta_album}, {"genre", required_argument, NULL, Meta_genre}, {"tracknum", required_argument, NULL, Meta_tracknum}, {"disknum", required_argument, NULL, Meta_disknum}, {"comment", required_argument, NULL, Meta_comment}, {"year", required_argument, NULL, Meta_year}, {"lyrics", required_argument, NULL, Meta_lyrics}, {"lyricsFile", required_argument, NULL, Meta_lyrics_file}, {"composer", required_argument, NULL, Meta_composer}, {"copyright", required_argument, NULL, Meta_copyright}, {"grouping", required_argument, NULL, Meta_grouping}, {"albumArtist", required_argument, NULL, Meta_album_artist}, {"compilation", required_argument, NULL, Meta_compilation}, {"hdvideo", required_argument, NULL, Meta_hdvideo}, {"advisory", required_argument, NULL, Meta_advisory}, {"bpm", required_argument, NULL, Meta_BPM}, {"artwork", required_argument, NULL, Meta_artwork}, {"stik", required_argument, NULL, Meta_stik}, {"description", required_argument, NULL, Meta_description}, {"longdesc", required_argument, NULL, Meta_longdescription}, {"storedesc", required_argument, NULL, Meta_storedescription}, {"Rating", required_argument, NULL, Meta_Rating}, {"TVNetwork", required_argument, NULL, Meta_TV_Network}, {"TVShowName", required_argument, NULL, Meta_TV_ShowName}, {"TVEpisode", required_argument, NULL, Meta_TV_Episode}, {"TVEpisodeNum", required_argument, NULL, Meta_TV_EpisodeNumber}, {"TVSeasonNum", required_argument, NULL, Meta_TV_SeasonNumber}, {"podcastFlag", required_argument, NULL, Meta_podcastFlag}, {"keyword", required_argument, NULL, Meta_keyword}, {"category", required_argument, NULL, Meta_category}, {"podcastURL", required_argument, NULL, Meta_podcast_URL}, {"podcastGUID", required_argument, NULL, Meta_podcast_GUID}, {"purchaseDate", required_argument, NULL, Meta_PurchaseDate}, {"encodingTool", required_argument, NULL, Meta_EncodingTool}, {"encodedBy", required_argument, NULL, Meta_EncodedBy}, {"apID", required_argument, NULL, Meta_apID}, {"cnID", required_argument, NULL, Meta_cnID}, {"geID", required_argument, NULL, Meta_geID}, {"xID", required_argument, NULL, Meta_xID}, {"gapless", required_argument, NULL, Meta_PlayGapless}, {"sortOrder", required_argument, NULL, Meta_SortOrder}, {"rDNSatom", required_argument, NULL, Meta_ReverseDNS_Form}, {"contentRating", required_argument, NULL, Meta_rDNS_rating}, {"tagtime", optional_argument, NULL, Meta_StandardDate}, {"information", required_argument, NULL, Meta_Information}, {"url", required_argument, NULL, Meta_URL}, {"meta-uuid", required_argument, NULL, Meta_uuid}, {"extract-uuids", optional_argument, NULL, Opt_Extract_all_uuids}, {"extract1uuid", required_argument, NULL, Opt_Extract_a_uuid}, {"iPod-uuid", required_argument, NULL, Opt_Ipod_AVC_uuid}, {"freefree", optional_argument, NULL, Opt_FreeFree}, {"metaEnema", 0, NULL, Metadata_Purge}, {"manualAtomRemove", required_argument, NULL, Manual_atom_removal}, {"udtaEnema", 0, NULL, UserData_Purge}, {"foobar2000Enema", 0, NULL, foobar_purge}, {"metaDump", 0, NULL, Meta_dump}, {"output", required_argument, NULL, OPT_OutputFile}, {"preventOptimizing", 0, NULL, OPT_NoOptimize}, {"overWrite", 0, NULL, OPT_OverWrite}, #if defined(_WIN32) {"preserveTime", 0, NULL, OPT_PreserveTimeStamps}, #endif {"ISO-copyright", required_argument, NULL, ISO_Copyright}, {"3gp-title", required_argument, NULL, _3GP_Title}, {"3gp-author", required_argument, NULL, _3GP_Author}, {"3gp-performer", required_argument, NULL, _3GP_Performer}, {"3gp-genre", required_argument, NULL, _3GP_Genre}, {"3gp-description", required_argument, NULL, _3GP_Description}, {"3gp-copyright", required_argument, NULL, _3GP_Copyright}, {"3gp-album", required_argument, NULL, _3GP_Album}, {"3gp-year", required_argument, NULL, _3GP_Year}, {"3gp-rating", required_argument, NULL, _3GP_Rating}, {"3gp-classification", required_argument, NULL, _3GP_Classification}, {"3gp-keyword", required_argument, NULL, _3GP_Keyword}, {"3gp-location", required_argument, NULL, _3GP_Location}, {"ID3Tag", required_argument, NULL, Meta_ID3v2Tag}, {"DeepScan", 0, &extr, 1}, {"movementCount", required_argument, NULL, Meta_movementCount}, {"movementName", required_argument, NULL, Meta_movementName}, {"movementNumber", required_argument, NULL, Meta_movementNumber}, {"showWorkMovement", required_argument, NULL, Meta_showWorkMovement}, {"work", required_argument, NULL, Meta_work}, {0, 0, 0, 0} }; int c = -1; int option_index = 0; c = getopt_long(argc, argv, "hTtEe:a:b:c:d:f:g:i:k:l:n:o:p:q::u:w:x:y:z:A:B:C:D:F:G:H:" "I:J:K:L:MN:QR:S:U:WXV:ZP", long_options, &option_index); if (c == -1) { if (argc < 3 && argc > 2) { APar_ScanAtoms(ISObasemediafile, true); APar_PrintAtomicTree(); } break; } signal(SIGTERM, kill_signal); signal(SIGINT, kill_signal); switch (c) { // "optind" represents the count of arguments up to and including its // optional flag: case '?': return 1; case OPT_HELP: { fprintf(stdout, "%s", longHelp_text); return 0; } case OPT_TEST: { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile, true); APar_PrintAtomicTree(); if (argv[optind]) { if (strcmp(argv[optind], "+dates") == 0) { APar_ExtractDetails(APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO + SHOW_DATE_INFO); } else { APar_ExtractDetails(APar_OpenISOBaseMediaFile(ISObasemediafile, true), SHOW_TRACK_INFO); } } APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case OPT_ShowTextData: { if (argv[optind]) { // for utilities that write iTunes-style metadata into // 3gp branded files APar_ExtractBrands(ISObasemediafile); deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); if (strcmp(argv[optind], "+") == 0) { APar_Print_iTunesData(ISObasemediafile, NULL, PRINT_FREE_SPACE + PRINT_PADDING_SPACE + PRINT_USER_DATA_SPACE + PRINT_MEDIA_SPACE, PRINT_DATA); } else { fprintf(stdout, "---------------------------\n"); APar_Print_ISO_UserData_per_track(); AtomicInfo *iTuneslistAtom = APar_FindAtom("moov.udta.meta.ilst", false, SIMPLE_ATOM, 0); if (iTuneslistAtom != NULL) { fprintf( stdout, "---------------------------\n iTunes-style metadata tags:\n"); APar_Print_iTunesData(ISObasemediafile, NULL, PRINT_FREE_SPACE + PRINT_PADDING_SPACE + PRINT_USER_DATA_SPACE + PRINT_MEDIA_SPACE, PRINT_DATA, iTuneslistAtom); } fprintf(stdout, "---------------------------\n"); } } else { deep_atom_scan = true; APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); if (metadata_style >= THIRD_GEN_PARTNER) { APar_PrintUserDataAssests(); } else if (metadata_style == ITUNES_STYLE) { APar_Print_iTunesData(ISObasemediafile, NULL, 0, PRINT_DATA); // false, don't try to extractPix APar_Print_APuuid_atoms(ISObasemediafile, NULL, PRINT_DATA); } } APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case OPT_ExtractPix: { char *base_path = (char *)malloc(sizeof(char) * MAXPATHLEN + 1); memset(base_path, 0, MAXPATHLEN + 1); GetBasePath(ISObasemediafile, base_path); APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_iTunesData( ISObasemediafile, base_path, 0, EXTRACT_ARTWORK); // exportPix to stripped ISObasemediafile path APar_OpenISOBaseMediaFile(ISObasemediafile, false); free(base_path); base_path = NULL; break; } case OPT_ExtractPixToPath: { APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_iTunesData(ISObasemediafile, optarg, 0, EXTRACT_ARTWORK); // exportPix to a different path APar_OpenISOBaseMediaFile(ISObasemediafile, false); break; } case Meta_artist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "artist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ART.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_artDirector: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "artDirector")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ard.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_arranger: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "arranger")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251arg.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_author: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "author")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251aut.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_conductor: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "conductor")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251con.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_director: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "director")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251dir.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_originalArtist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "originalArtist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251ope.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_producer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "producer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251prd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } /* case Meta_performer : { APar_ScanAtoms(ISObasemediafile); if ( !APar_assert(metadata_style == ITUNES_STYLE, 1, "performer") ) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo* artistData_atom = APar_MetaData_atom_Init("moov.udta.meta.ilst.\251prf.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } */ case Meta_soundEngineer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "soundEngineer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251sne.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_soloist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "soloist")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251sol.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_executiveProducer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "executiveProducer")) { char major_brand[4]; UInt32_TO_String4(brand, &*major_brand); APar_assert(false, 4, &*major_brand); break; } AtomicInfo *artistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251xpd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( artistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_songtitle: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "title")) { break; } AtomicInfo *titleData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251nam.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( titleData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_subtitle: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "subtitle")) { break; } AtomicInfo *titleData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251st3.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( titleData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_album: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "album")) { break; } AtomicInfo *albumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251alb.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( albumData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_genre: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "genre")) { break; } APar_MetaData_atomGenre_Set(optarg); break; } case Meta_tracknum: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "track number")) { break; } uint16_t pos_in_total = 0; uint16_t the_total = 0; if (strrchr(optarg, '/') != NULL) { char *duplicate_info = optarg; char *item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &pos_in_total); item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &the_total); } else { sscanf(optarg, "%" SCNu16, &pos_in_total); } AtomicInfo *tracknumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.trkn.data", optarg, AtomFlags_Data_Binary); // tracknum: [0, 0, 0, 0, 0, 0, 0, pos_in_total, 0, the_total, 0, 0]; // BUT that first uint32_t is already accounted for in // APar_MetaData_atom_Init APar_Unified_atom_Put( tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, pos_in_total, 16); APar_Unified_atom_Put(tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, the_total, 16); APar_Unified_atom_Put( tracknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); break; } case Meta_disknum: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "disc number")) { break; } uint16_t pos_in_total = 0; uint16_t the_total = 0; if (strrchr(optarg, '/') != NULL) { char *duplicate_info = optarg; char *item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &pos_in_total); item_stat = strsep(&duplicate_info, "/"); sscanf(item_stat, "%" SCNu16, &the_total); } else { sscanf(optarg, "%" SCNu16, &pos_in_total); } AtomicInfo *disknumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.disk.data", optarg, AtomFlags_Data_Binary); // disknum: [0, 0, 0, 0, 0, 0, 0, pos_in_total, 0, the_total]; BUT that // first uint32_t is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, pos_in_total, 16); APar_Unified_atom_Put(disknumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, the_total, 16); break; } case Meta_comment: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "comment")) { break; } AtomicInfo *commentData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251cmt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( commentData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_year: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "year")) { break; } AtomicInfo *yearData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251day.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( yearData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_lyrics: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "lyrics")) { break; } AtomicInfo *lyricsData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251lyr.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( lyricsData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_lyrics_file: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "lyrics")) { break; } APar_MetaData_atomLyrics_Set(optarg); break; } case Meta_composer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "composer")) { break; } AtomicInfo *composerData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251wrt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( composerData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_copyright: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "copyright")) { break; } AtomicInfo *copyrightData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cprt.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( copyrightData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_grouping: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "grouping")) { break; } AtomicInfo *groupingData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251grp.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( groupingData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_compilation: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "compilation")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.cpil.data", VERSIONED_ATOM, 0); } else { // compilation: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *compilationData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cpil.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(compilationData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); // a hard coded uint8_t of: 1 is compilation } break; } case Meta_hdvideo: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "hdvideo")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0 || strcmp(optarg, "0") == 0) { APar_RemoveAtom("moov.udta.meta.ilst.hdvd.data", VERSIONED_ATOM, 0); } else { // compilation: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *hdvideoData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.hdvd.data", optarg, AtomFlags_Data_UInt); uint8_t hdvideo_value = 0; if (strcmp(optarg, "true") == 0) { hdvideo_value = 1; } else { sscanf(optarg, "%" SCNu8, &hdvideo_value); } APar_Unified_atom_Put(hdvideoData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, hdvideo_value, 8); } break; } case Meta_BPM: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "BPM")) { break; } if (strcmp(optarg, "0") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.tmpo.data", VERSIONED_ATOM, 0); } else { uint16_t bpm_value = 0; sscanf(optarg, "%" SCNu16, &bpm_value); // bpm is [0, 0, 0, 0, 0, bpm_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *bpmData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tmpo.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(bpmData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, bpm_value, 16); } break; } case Meta_advisory: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "content advisory")) { break; } if (strcmp(optarg, "remove") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.rtng.data", VERSIONED_ATOM, 0); } else { uint8_t rating_value = 0; if (strcmp(optarg, "clean") == 0) { rating_value = 2; // only \02 is clean } else if (strcmp(optarg, "explicit") == 0) { rating_value = 4; // most non \00, \02 numbers are allowed } // rating is [0, 0, 0, 0, rating_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *advisoryData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.rtng.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(advisoryData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, rating_value, 8); } break; } case Meta_artwork: { // handled differently: there can be multiple // "moov.udta.meta.ilst.covr.data" atoms char *env_PicOptions = getenv("PIC_OPTIONS"); APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "coverart")) { break; } APar_MetaData_atomArtwork_Set(optarg, env_PicOptions); break; } case Meta_stik: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "'stik'")) { break; } if (strcmp(optarg, "remove") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.stik.data", VERSIONED_ATOM, 0); } else { uint8_t stik_value = 0; if (strncmp(optarg, "value=", 6) == 0) { char *stik_val_str_ptr = optarg; strsep(&stik_val_str_ptr, "="); stik_value = strtoul(stik_val_str_ptr, NULL, 10); } else { stiks *return_stik = MatchStikString(optarg); if (return_stik != NULL) { stik_value = return_stik->stik_number; if (strcmp(optarg, "Audiobook") == 0) { forced_suffix_type = FORCE_M4B_TYPE; } } } // stik is [0, 0, 0, 0, stik_value]; BUT that first uint32_t is // already accounted for in APar_MetaData_atom_Init AtomicInfo *stikData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.stik.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(stikData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, stik_value, 8); } break; } case Meta_EncodingTool: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "encoding tool")) { break; } AtomicInfo *encodingtoolData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251too.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put(encodingtoolData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_EncodedBy: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "encoded by")) { break; } AtomicInfo *encodedbyData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251enc.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( encodedbyData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_apID: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "Account Name")) { break; } AtomicInfo *apIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.apID.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( apIDData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_description: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "description")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.desc.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_longdescription: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "longdesc")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.ldes.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_storedescription: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "storedesc")) { break; } AtomicInfo *descriptionData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sdes.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( descriptionData_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_Rating: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "Rating")) { break; } AtomicInfo *ratingData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.rate.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( ratingData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_Network: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Network")) { break; } AtomicInfo *tvnetworkData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvnn.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvnetworkData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_ShowName: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Show name")) { break; } AtomicInfo *tvshownameData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvsh.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvshownameData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_Episode: { // if the show "ABC Lost 209", its "209" APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "TV Episode string")) { break; } AtomicInfo *tvepisodeData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tven.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( tvepisodeData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_TV_SeasonNumber: { // if the show "ABC Lost 209", its 2; integer 2 // not char "2" APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "TV Season")) { break; } uint16_t data_value = 0; sscanf(optarg, "%" SCNu16, &data_value); AtomicInfo *tvseasonData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tvsn.data", optarg, AtomFlags_Data_UInt); // season is [0, 0, 0, 0, 0, 0, 0, data_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tvseasonData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 16); break; } case Meta_TV_EpisodeNumber: { // if the show "ABC Lost 209", its 9; integer // 9 (0x09) not char "9"(0x39) APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "TV Episode number")) { break; } uint16_t data_value = 0; sscanf(optarg, "%" SCNu16, &data_value); AtomicInfo *tvepisodenumData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.tves.data", optarg, AtomFlags_Data_UInt); // episodenumber is [0, 0, 0, 0, 0, 0, 0, data_value]; BUT that first // uint32_t is already accounted for in APar_MetaData_atom_Init APar_Unified_atom_Put( tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 0, 16); APar_Unified_atom_Put(tvepisodenumData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 16); break; } case Meta_cnID: { // the iTunes Catalog ID APar_ScanAtoms(ISObasemediafile); if (!APar_assert( metadata_style == ITUNES_STYLE, 1, "iTunes Catalog ID")) { break; } uint32_t data_value = 0; sscanf(optarg, "%" SCNu32, &data_value); AtomicInfo *cnIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.cnID.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(cnIDData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 32); break; } case Meta_geID: { // the iTunes Genre ID APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "iTunes Genre ID")) { break; } uint32_t data_value = 0; sscanf(optarg, "%" SCNu32, &data_value); AtomicInfo *geIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.geID.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put(geIDData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 32); break; } case Meta_xID: { // the vendor-supplied iTunes xID APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "iTunes xID")) { break; } AtomicInfo *xIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.xid .data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( xIDData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_album_artist: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "album artist")) { break; } AtomicInfo *albumartistData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.aART.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( albumartistData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_podcastFlag: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast flag")) { break; } if (strcmp(optarg, "false") == 0) { APar_RemoveAtom("moov.udta.meta.ilst.pcst.data", VERSIONED_ATOM, 0); } else { // podcastflag: [0, 0, 0, 0, boolean_value]; BUT that first uint32_t // is already accounted for in APar_MetaData_atom_Init AtomicInfo *podcastFlagData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.pcst.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( podcastFlagData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); // a hard coded uint8_t of: 1 denotes podcast flag } break; } case Meta_keyword: { // TODO to the end of iTunes-style metadata & uuid // atoms APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "keyword")) { break; } AtomicInfo *keywordData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.keyw.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( keywordData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_category: { // see // http://www.apple.com/itunes/podcasts/techspecs.html // for available categories APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "category")) { break; } AtomicInfo *categoryData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.catg.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( categoryData_atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_podcast_URL: { // usually a read-only value, but useful for // getting videos into the 'podcast' menu APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast URL")) { break; } AtomicInfo *podcasturlData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.purl.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( podcasturlData_atom, optarg, UTF8_iTunesStyle_Binary, 0, 0); break; } case Meta_podcast_GUID: { // Global Unique IDentifier; it is *highly* // doubtful that this would be useful... APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "podcast GUID")) { break; } AtomicInfo *globalidData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.egid.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( globalidData_atom, optarg, UTF8_iTunesStyle_Binary, 0, 0); break; } case Meta_PurchaseDate: { // might be useful to *remove* this, but adding // it... although it could function like id3v2 // tdtg... APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "purchase date")) { break; } char *purd_time; bool free_memory = false; if (optarg != NULL) { if (strcmp(optarg, "timestamp") == 0) { purd_time = (char *)malloc(sizeof(char) * 255); free_memory = true; APar_StandardTime(purd_time); } else { purd_time = optarg; } } else { purd_time = optarg; } AtomicInfo *globalIDData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.purd.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( globalIDData_atom, purd_time, UTF8_iTunesStyle_256glyphLimited, 0, 0); if (free_memory) { free(purd_time); purd_time = NULL; } break; } case Meta_PlayGapless: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "gapless playback")) { break; } if (strcmp(optarg, "false") == 0 || strlen(optarg) == 0) { APar_RemoveAtom("moov.udta.meta.ilst.pgap.data", VERSIONED_ATOM, 0); } else { // gapless playback: [0, 0, 0, 0, boolean_value]; BUT that first // uint32_t is already accounted for in APar_MetaData_atom_Init AtomicInfo *gaplessData_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.pgap.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( gaplessData_atom, NULL, UTF8_iTunesStyle_256glyphLimited, 1, 8); } break; } case Meta_SortOrder: { AtomicInfo *sortOrder_atom = NULL; APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "sort order tags")) { break; } if (argv[optind] == NULL) { fprintf(stdout, "AP warning, skipping setting the sort order %s tag\n", optarg); break; } if (strcmp(optarg, "name") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sonm.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "artist") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soar.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "albumartist") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soaa.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "album") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soal.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "composer") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.soco.data", argv[optind], AtomFlags_Data_Text); } else if (strcmp(optarg, "show") == 0) { sortOrder_atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.sosn.data", argv[optind], AtomFlags_Data_Text); } APar_Unified_atom_Put( sortOrder_atom, argv[optind], UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } // uuid atoms case Meta_StandardDate: { APar_ScanAtoms(ISObasemediafile); char *formed_time = (char *)malloc(sizeof(char) * 110); if (argv[optind]) { if (strlen(argv[optind]) > 0) { APar_StandardTime(formed_time); } } AtomicInfo *tdtgUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "tdtg", AtomFlags_Data_Text, formed_time, false); APar_Unified_atom_Put( tdtgUUID, formed_time, UTF8_iTunesStyle_Unlimited, 0, 0); free(formed_time); break; } case Meta_URL: { APar_ScanAtoms(ISObasemediafile); AtomicInfo *urlUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "\251url", AtomFlags_Data_Text, optarg, false); APar_Unified_atom_Put(urlUUID, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_Information: { APar_ScanAtoms(ISObasemediafile); AtomicInfo *infoUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", "\251inf", AtomFlags_Data_Text, optarg, false); APar_Unified_atom_Put(infoUUID, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); break; } case Meta_uuid: { APar_ScanAtoms(ISObasemediafile); uint32_t uuid_dataType = 0; uint32_t desc_len = 0; uint8_t mime_len = 0; char *uuid_file_path = NULL; char *uuid_file_description = NULL; char *uuid_file_extn = NULL; char *uuid_file_mimetype = NULL; // char* uuid_file_filename = NULL; // a uuid in AP is a version 5 uuid created by getting a sha1 hash // of a string (the atom name) in a namespace ('AP.sf.net'). This // is guaranteed to be reproducible, so later it can be verified // that this uuid (which could come from anywhere), is in fact made // by AtomicParsley. This is achieved by storing the atom name // string right after the uuid, and is read back later and a new // uuid is created to see if it matches the discovered uuid. If // they match, it will print out or extract to a file; if not, only // its name will be displayed in the tree. // // --meta-uuid "\251foo" 1 'http://www.url.org' --meta-uuid "pdf1" // file /some/path/pic.pdf description="My Booty, Your Booty, // Djbouti" if (strcmp(argv[optind], "text") == 0 || strcmp(argv[optind], "1") == 0) uuid_dataType = AtomFlags_Data_Text; if (strcmp(argv[optind], "file") == 0) { uuid_dataType = AtomFlags_Data_uuid_binary; if (optind + 1 < argc) { if (true) { // TODO: test the file to see if it exists uuid_file_path = argv[optind + 1]; // get the file extension/suffix of the file to embed uuid_file_extn = strrchr(uuid_file_path, '.'); //'.' inclusive; say goodbye to AP-0.8.8.tar.bz2 //#ifdef _WIN32 //#define path_delim '\\' //#else //#define path_delim '/' //#endif // uuid_file_filename = strrchr(uuid_file_path, // path_delim)+1; //includes whatever // extensions } if (uuid_file_extn == NULL) { fprintf(stdout, "AP warning: embedding a file onto a uuid atom " "requires a file extension. Skipping.\n"); continue; } // copy a pointer to description int more_optional_args = 2; while (optind + more_optional_args < argc) { if (strncmp( argv[optind + more_optional_args], "description=", 12) == 0 && argv[optind + more_optional_args][12]) { uuid_file_description = argv[optind + more_optional_args] + 12; desc_len = strlen(uuid_file_description) + 1; //+1 for the trailing 1 byte NULL terminator } if (strncmp(argv[optind + more_optional_args], "mime-type=", 10) == 0 && argv[optind + more_optional_args][10]) { uuid_file_mimetype = argv[optind + more_optional_args] + 10; mime_len = strlen(uuid_file_mimetype) + 1; //+1 for the trailing 1 byte NULL terminator } if (strncmp(argv[optind + more_optional_args], "--", 2) == 0) { break; // we've hit another cli argument } more_optional_args++; } } } AtomicInfo *genericUUID = APar_uuid_atom_Init("moov.udta.meta.uuid=%s", optarg, uuid_dataType, argv[optind + 1], true); if (uuid_dataType == AtomFlags_Data_uuid_binary && genericUUID != NULL) { TestFileExistence(uuid_file_path, true); // format for a uuid atom set by AP: // 4 bytes - atom length as uin32_t // 4 bytes - atom name as iso 8859-1 atom name as a 4byte string set // to 'uuid' 16 bytes - the uuid; here a version 5 sha1-based hash // derived from a name in a namespace of 'AtomicParsley.sf.net' 4 bytes // - the name of the desired atom to create a uuid for (this "name" of // the uuid is the only cli accessible means of crafting the uuid) 4 // bytes - atom version & flags (currently 1 for 'text' or '88' for // binary attachment) 4 bytes - NULL space /////////////text or 1 for version/flags // X bytes - utf8 string, no null termination /////////////binary attachment or 88 for version/flags // 4 bytes - length of utf8 string describing the attachment // X bytes - utf8 string describing the attachment, null terminated // 1 byte - length of the file suffix (including the period) of the // originating file X bytes - utf8 string of the file suffix, null // terminated 1 byte - length of the MIME-type X bytes - utf8 string // holding the MIME-type, null terminated 4 bytes - length of the // attached binary data/file length X bytes - binary data uint32_t extn_len = strlen(uuid_file_extn) + 1; //+1 for the trailing 1 byte NULL terminator uint64_t file_len = findFileSize(uuid_file_path); APar_MetaData_atom_QuickInit(genericUUID->AtomicNumber, uuid_dataType, 20, extn_len + desc_len + file_len + 100); genericUUID->AtomicClassification = EXTENDED_ATOM; // it gets reset in QuickInit Above; force its proper // setting if (uuid_file_description == NULL || desc_len == 0) { APar_Unified_atom_Put( genericUUID, "[none]", UTF8_3GP_Style, 7, 32); // sets 4bytes desc_len, then 7bytes description (forced to // "[none]"=6 + 1 byte NULL =7) } else { APar_Unified_atom_Put(genericUUID, uuid_file_description, UTF8_3GP_Style, desc_len, 32); // sets 4bytes desc_len, then Xbytes // description (in that order) } APar_Unified_atom_Put(genericUUID, uuid_file_extn, UTF8_3GP_Style, extn_len, 8); // sets 1bytes desc_len, then Xbytes file // extension string (in that order) if (uuid_file_mimetype == NULL || mime_len == 0) { APar_Unified_atom_Put( genericUUID, "none", UTF8_3GP_Style, 5, 8); // sets 4bytes desc_len, then 5bytes description (forced to // "none" + 1byte null) } else { APar_Unified_atom_Put( genericUUID, uuid_file_mimetype, UTF8_3GP_Style, mime_len, 8); // sets 1 byte mime len, then Xbytes mime type } FILE *uuid_binfile = APar_OpenFile(uuid_file_path, "rb"); APar_Unified_atom_Put(genericUUID, NULL, UTF8_3GP_Style, file_len, 32); // store the data directly on the atom in AtomicData uint32_t bin_bytes_read = APar_ReadFile( genericUUID->AtomicData + (genericUUID->AtomicLength - 32), uuid_binfile, file_len); genericUUID->AtomicLength += bin_bytes_read; fclose(uuid_binfile); } else { // text type APar_Unified_atom_Put( genericUUID, argv[optind + 1], UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Opt_Extract_all_uuids: { APar_ScanAtoms(ISObasemediafile); char *output_path = NULL; if (optind + 1 == argc) { output_path = argv[optind]; } APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Print_APuuid_atoms( ISObasemediafile, output_path, EXTRACT_ALL_UUID_BINARYS); APar_OpenISOBaseMediaFile(ISObasemediafile, false); exit(0); // never gets here break; } case Opt_Extract_a_uuid: { APar_ScanAtoms(ISObasemediafile); char *uuid_path = (char *)calloc(1, sizeof(char) * 256 + 1); char *uuid_binary_str = (char *)calloc(1, sizeof(char) * 20 + 1); char uuid_4char_name[16]; memset(uuid_4char_name, 0, 16); AtomicInfo *extractionAtom = NULL; UTF8Toisolat1((unsigned char *)&uuid_4char_name, 4, (unsigned char *)optarg, strlen(optarg)); APar_generate_uuid_from_atomname(uuid_4char_name, uuid_binary_str); // this will only append (and knock off) %s (anything) at the end of a // string uint16_t path_len = strlen("moov.udta.meta.uuid=%s"); memcpy(uuid_path, "moov.udta.meta.uuid=%s", path_len - 2); memcpy(uuid_path + (path_len - 2), uuid_binary_str, 16); extractionAtom = APar_FindAtom(uuid_path, false, EXTENDED_ATOM, 0, true); if (extractionAtom != NULL) { APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_Extract_uuid_binary_file(extractionAtom, ISObasemediafile, NULL); APar_OpenISOBaseMediaFile(ISObasemediafile, false); } free(uuid_path); uuid_path = NULL; free(uuid_binary_str); uuid_binary_str = NULL; exit(0); break; // never gets here } case Opt_Ipod_AVC_uuid: { if (deep_atom_scan == true) { if (strcmp(optarg, "1200") != 0) { fprintf(stdout, "the ipod-uuid has a single preset of '1200' which " "is required\n"); // 1200 might not be the only max // macroblock setting down the pike break; } uint8_t total_tracks = 0; uint8_t a_track = 0; // unused char atom_path[100]; AtomicInfo *video_desc_atom = NULL; memset(atom_path, 0, 100); APar_FindAtomInTrack( total_tracks, a_track, NULL); // With track_num set to 0, it will return the total trak // atom into total_tracks here. while (a_track < total_tracks) { a_track++; sprintf(atom_path, "moov.trak[%u].mdia.minf.stbl.stsd.avc1", a_track); video_desc_atom = APar_FindAtom(atom_path, false, VERSIONED_ATOM, 0, false); if (video_desc_atom != NULL) { uint16_t mb_t = APar_TestVideoDescription( video_desc_atom, APar_OpenFile(ISObasemediafile, "rb")); if (mb_t > 0 && mb_t <= 1200) { sprintf(atom_path, "moov.trak[%u].mdia.minf.stbl.stsd.avc1.uuid=", a_track); uint8_t uuid_baselen = (uint8_t)strlen(atom_path); APar_uuid_scanf(atom_path + uuid_baselen, "6b6840f2-5f24-4fc5-ba39-a51bcf0323f3"); APar_endian_uuid_bin_str_conversion(atom_path + uuid_baselen); APar_Generate_iPod_uuid(atom_path); } } } } else { fprintf( stdout, "the --DeepScan option is required for this operation. Skipping\n"); } break; } case Manual_atom_removal: { APar_ScanAtoms(ISObasemediafile); char *compliant_name = (char *)malloc(sizeof(char) * strlen(optarg) + 1); memset(compliant_name, 0, strlen(optarg) + 1); UTF8Toisolat1((unsigned char *)compliant_name, strlen(optarg), (unsigned char *)optarg, strlen(optarg)); if (strstr(optarg, "uuid=") != NULL) { uint16_t uuid_path_pos = 0; uint16_t uuid_path_len = strlen(optarg); while (compliant_name + uuid_path_pos < compliant_name + uuid_path_len) { if (strncmp(compliant_name + uuid_path_pos, "uuid=", 5) == 0) { uuid_path_pos += 5; break; } uuid_path_pos++; } if (strlen(compliant_name + uuid_path_pos) > 4) { // if =4 then it would be the deprecated form (or it should be, // if not it just won't find anything; no harm done) uint8_t uuid_len = APar_uuid_scanf(compliant_name + uuid_path_pos, optarg + uuid_path_pos); compliant_name[uuid_path_pos + uuid_len] = 0; } APar_RemoveAtom(compliant_name, EXTENDED_ATOM, 0); } else if (strcmp(compliant_name + (strlen(compliant_name) - 4), "data") == 0) { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); } else { size_t string_len = strlen(compliant_name); // reverseDNS atom path if (strstr(optarg, ":[") != NULL && compliant_name[string_len - 1] == ']') { APar_RemoveAtom(compliant_name, VERSIONED_ATOM, 0); // packed language asset } else if (strncmp(compliant_name + string_len - 9, ":lang=", 6) == 0) { uint16_t packed_lang = PackLanguage(compliant_name + string_len - 3, 0); memset(compliant_name + string_len - 9, 0, 1); APar_RemoveAtom(compliant_name, PACKED_LANG_ATOM, packed_lang); } else { APar_RemoveAtom(compliant_name, UNKNOWN_ATOM, 0); } } free(compliant_name); compliant_name = NULL; break; } // 3gp tags /* First, scan the file to get at atom tree (only happens once). Then take the cli args and look for optional arguments. All arguments begin with -- or -; other args are optional and are determined by directly testing arguments. Optional arguments common to the 3gp asset group (language, unicode, track/movie userdata) are extracted in find_optional_args. Setting assets in all tracks requires getting the number of tracks. Loop through either once (for movie & single track) or as many tracks there are for all tracks. Each pass through the loop, set the individual pieces of metadata. */ case _3GP_Title: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "title")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; // otherwise, APar_UserData_atom_Init will shift // to non-existing track 0 } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *title_asset = APar_UserData_atom_Init( "titl", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( title_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Author: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "author")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *author_asset = APar_UserData_atom_Init( "auth", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( author_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Performer: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "performer")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *performer_asset = APar_UserData_atom_Init( "perf", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( performer_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Genre: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "genre")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *genre_asset = APar_UserData_atom_Init( "gnre", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( genre_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Description: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "description")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *description_asset = APar_UserData_atom_Init( "dscp", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( description_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case ISO_Copyright: // ISO copyright atom common to all files that are // derivatives of the base media file format, identical // to.... case _3GP_Copyright: { // the 3gp copyright asset; this gets a test for // major branding (but only with the cli arg // --3gp-copyright). APar_ScanAtoms(ISObasemediafile); if (c == _3GP_Copyright) { if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "copyright")) { break; } } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 3); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *copyright_notice = APar_UserData_atom_Init( "cprt", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( copyright_notice, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Album: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "album")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; uint8_t tracknum = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 4); // cygle through the remaining independant arguments (before the next // --cli_flag) and figure out if any are useful to us; already have lang & // utf16 for (int i = 0; i <= 4; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "trknum=", 7) == 0) { char *track_num = argv[optind + i]; strsep(&track_num, "="); tracknum = strtoul(track_num, NULL, 10); } if (*argv[optind + i] == '-') break; } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *album_asset = APar_UserData_atom_Init( "albm", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( album_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); if (tracknum != 0) { APar_Unified_atom_Put(album_asset, NULL, UTF8_3GP_Style, tracknum, 8); } } break; } case _3GP_Year: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "year")) { break; } uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; uint16_t year_tag = 0; if (argv[optind] && optind <= total_args) { if (strcmp(argv[optind], "movie") == 0) { userdata_area = MOVIE_LEVEL_ATOM; } if (strncmp(argv[optind], "track=", 6) == 0) { char *trak_idx = argv[optind]; strsep(&trak_idx, "="); selected_track = strtoul(trak_idx, NULL, 10); userdata_area = SINGLE_TRACK_ATOM; } else if (strcmp(argv[optind], "track") == 0) { userdata_area = ALL_TRACKS_ATOM; } } sscanf(optarg, "%" SCNu16, &year_tag); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *recordingyear_asset = APar_UserData_atom_Init( "yrrc", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, 0); APar_Unified_atom_Put( recordingyear_asset, NULL, UTF8_3GP_Style, year_tag, 16); } break; } case _3GP_Rating: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "rating")) { break; } char rating_entity[5] = { 'N', 'O', 'N', 'E', 0}; //'NONE' - thats what it will be if not provided char rating_criteria[5] = {'N', 'O', 'N', 'E', 0}; bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 5); for (int i = 0; i < 5; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "entity=", 7) == 0) { char *entity = argv[optind + i]; strsep(&entity, "="); memcpy(&rating_entity, entity, 4); } if (strncmp(argv[optind + i], "criteria=", 9) == 0) { char *criteria = argv[optind + i]; strsep(&criteria, "="); memcpy(&rating_criteria, criteria, 4); } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *rating_asset = APar_UserData_atom_Init( "rtng", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put(rating_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(rating_entity), 32); APar_Unified_atom_Put(rating_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(rating_criteria), 32); APar_Unified_atom_Put( rating_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Classification: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "classification")) { break; } char classification_entity[5] = { 'N', 'O', 'N', 'E', 0}; //'NONE' - thats what it will be if not provided uint16_t classification_index = 0; bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 5); for (int i = 0; i < 4; i++) { // 3 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "entity=", 7) == 0) { char *cls_entity = argv[optind + i]; strsep(&cls_entity, "="); memcpy(&classification_entity, cls_entity, 4); } if (strncmp(argv[optind + i], "index=", 6) == 0) { char *cls_idx = argv[optind + i]; strsep(&cls_idx, "="); sscanf(cls_idx, "%" SCNu16, &classification_index); } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *classification_asset = APar_UserData_atom_Init( "clsf", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put(classification_asset, NULL, UTF8_3GP_Style, UInt32FromBigEndian(classification_entity), 32); APar_Unified_atom_Put(classification_asset, NULL, UTF8_3GP_Style, classification_index, 16); APar_Unified_atom_Put( classification_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); } break; } case _3GP_Keyword: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "keyword")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; char *formed_keyword_struct = NULL; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 4); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } if (strrchr(optarg, '=') != NULL) { // must be in the format of: keywords=foo1,foo2,foo3,foo4 char *arg_keywords = optarg; char *keywords_globbed = strsep(&arg_keywords, "="); // separate out 'keyword=' keywords_globbed = strsep(&arg_keywords, "="); // this is what we want to work on: just the keywords char *keyword_ptr = keywords_globbed; uint32_t keyword_strlen = strlen(keywords_globbed); uint8_t keyword_count = 0; uint32_t key_index = 0; if (keyword_strlen > 0) { // if there is anything past the = then it counts as a keyword keyword_count++; } while (true) { // count occurrences of comma here if (*keyword_ptr == ',') { keyword_count++; } keyword_ptr++; key_index++; if (keyword_strlen == key_index) { break; } } formed_keyword_struct = (char *)calloc( 1, sizeof(char) * set_UTF16_text ? (keyword_strlen * 4) : (keyword_strlen * 2)); // *4 should carry utf16's BOM & TERM uint32_t keyword_struct_bytes = APar_3GP_Keyword_atom_Format(keywords_globbed, keyword_count, set_UTF16_text, formed_keyword_struct); for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { AtomicInfo *keyword_asset = APar_UserData_atom_Init( "kywd", keyword_strlen ? "temporary" : "", userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); // just a "temporary" valid string to satisfy a test // there if (keyword_strlen > 0) { APar_Unified_atom_Put( keyword_asset, NULL, UTF8_3GP_Style, packed_lang, 16); APar_Unified_atom_Put( keyword_asset, NULL, UTF8_3GP_Style, keyword_count, 8); APar_atom_Binary_Put( keyword_asset, formed_keyword_struct, keyword_struct_bytes, 3); } } if (formed_keyword_struct != NULL) { free(formed_keyword_struct); formed_keyword_struct = NULL; } } else { for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { APar_UserData_atom_Init("kywd", "", userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); } } break; } case _3GP_Location: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style >= THIRD_GEN_PARTNER && metadata_style < MOTIONJPEG2000, 2, "location")) { break; } bool set_UTF16_text = false; uint16_t packed_lang = 0; uint8_t userdata_area = MOVIE_LEVEL_ATOM; uint8_t selected_track = 0; uint8_t a_track = 0; // unused uint8_t asset_iterations = 0; double longitude = -73.98; // if you don't provide a place, you WILL be put right into // Central Park. Welcome to New York City... now go away. double latitude = 40.77; double altitude = 4.3; uint8_t role = 0; const char *astronomical_body = "Earth"; const char *additional_notes = "no notes"; find_optional_args(argv, optind, packed_lang, set_UTF16_text, userdata_area, selected_track, 10); for (int i = 0; i <= 10; i++) { // 9 possible arguments for this tag (the first - which // doesn't count - is the data for the tag itself) if (argv[optind + i] && optind + i <= total_args) { if (strncmp(argv[optind + i], "longitude=", 10) == 0) { char *_long = argv[optind + i]; strsep(&_long, "="); sscanf(_long, "%lf", &longitude); if (_long[strlen(_long) - 1] == 'W') { longitude *= -1; } } if (strncmp(argv[optind + i], "latitude=", 9) == 0) { char *_latt = argv[optind + i]; strsep(&_latt, "="); sscanf(_latt, "%lf", &latitude); if (_latt[strlen(_latt) - 1] == 'S') { latitude *= -1; } } if (strncmp(argv[optind + i], "altitude=", 9) == 0) { char *_alti = argv[optind + i]; strsep(&_alti, "="); sscanf(_alti, "%lf", &altitude); if (_alti[strlen(_alti) - 1] == 'B') { altitude *= -1; } } if (strncmp(argv[optind + i], "role=", 5) == 0) { char *_role = argv[optind + i]; strsep(&_role, "="); if (strcmp(_role, "shooting location") == 0 || strcmp(_role, "shooting") == 0) { role = 0; } else if (strcmp(_role, "real location") == 0 || strcmp(_role, "real") == 0) { role = 1; } else if (strcmp(_role, "fictional location") == 0 || strcmp(_role, "fictional") == 0) { role = 2; } } if (strncmp(argv[optind + i], "body=", 5) == 0) { char *_astrobody = argv[optind + i]; strsep(&_astrobody, "="); astronomical_body = _astrobody; } if (strncmp(argv[optind + i], "notes=", 6) == 0) { char *_add_notes = argv[optind + i]; strsep(&_add_notes, "="); additional_notes = _add_notes; } if (*argv[optind + i] == '-') break; // we've hit another cli argument } } // fprintf(stdout, "long, lat, alt = %lf %lf %lf\n", longitude, latitude, // altitude); if (userdata_area == MOVIE_LEVEL_ATOM || userdata_area == SINGLE_TRACK_ATOM) { asset_iterations = 1; } else if (userdata_area == ALL_TRACKS_ATOM) { APar_FindAtomInTrack( asset_iterations, a_track, NULL); // With asset_iterations set to 0, it will return the total // trak atom into total_tracks here. if (asset_iterations == 1) selected_track = 1; } if (longitude < -180.0 || longitude > 180.0 || latitude < -90.0 || latitude > 90.0) { fprintf(stdout, "AtomicParsley warning: longitude or latitude was " "invalid; skipping setting location\n"); } else { for (uint8_t i_asset = 1; i_asset <= asset_iterations; i_asset++) { // short location_3GP_atom = APar_UserData_atom_Init("moov.udta.loci", // optarg, packed_lang); AtomicInfo *location_asset = APar_UserData_atom_Init( "loci", optarg, userdata_area, asset_iterations == 1 ? selected_track : i_asset, packed_lang); APar_Unified_atom_Put( location_asset, optarg, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), packed_lang, 16); APar_Unified_atom_Put(location_asset, NULL, false, role, 8); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(longitude), 32); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(latitude), 32); APar_Unified_atom_Put(location_asset, NULL, false, float_to_16x16bit_fixed_point(altitude), 32); APar_Unified_atom_Put( location_asset, astronomical_body, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), 0, 0); APar_Unified_atom_Put( location_asset, additional_notes, (set_UTF16_text ? UTF16_3GP_Style : UTF8_3GP_Style), 0, 0); } } break; } case Meta_ReverseDNS_Form: { //--rDNSatom "mv-ma" name=iTuneEXTC // domain=com.apple.iTunes char *reverseDNS_atomname = NULL; char *reverseDNS_atomdomain = NULL; uint32_t rdns_atom_flags = AtomFlags_Data_Text; APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "reverse DNS form")) { break; } for (int i = 0; i <= 5 - 1; i++) { if (argv[optind + i] && optind + i <= argc) { if (strncmp(argv[optind + i], "name=", 5) == 0) { reverseDNS_atomname = argv[optind + i] + 5; } else if (strncmp(argv[optind + i], "domain=", 7) == 0) { reverseDNS_atomdomain = argv[optind + i] + 7; } else if (strncmp(argv[optind + i], "datatype=", 9) == 0) { sscanf(argv[optind + i] + 9, "%" SCNu32, &rdns_atom_flags); } if (*argv[optind + i] == '-') { break; // we've hit another cli argument } } } if (reverseDNS_atomname == NULL) { fprintf(stdout, "AtomicParsley warning: no name for the reverseDNS " "form was found. Skipping.\n"); } else if ((int)strlen(reverseDNS_atomname) != test_conforming_alpha_string(reverseDNS_atomname)) { fprintf(stdout, "AtomicParsley warning: Some part of the reverseDNS " "atom name was non-conforming. Skipping.\n"); } else if (reverseDNS_atomdomain == NULL) { fprintf(stdout, "AtomicParsley warning: no domain for the reverseDNS " "form was found. Skipping.\n"); } else if (rdns_atom_flags != AtomFlags_Data_Text) { fprintf(stdout, "AtomicParsley warning: currently, only the strings " "are supported in reverseDNS atoms. Skipping.\n"); } else { AtomicInfo *rDNS_data_atom = APar_reverseDNS_atom_Init(reverseDNS_atomname, optarg, &rdns_atom_flags, reverseDNS_atomdomain); APar_Unified_atom_Put( rDNS_data_atom, optarg, UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Meta_rDNS_rating: { const char *media_rating = Expand_cli_mediastring(optarg); uint32_t rDNS_data_flags = AtomFlags_Data_Text; APar_ScanAtoms(ISObasemediafile); if (media_rating != NULL || strlen(optarg) == 0) { AtomicInfo *rDNS_rating_data_atom = APar_reverseDNS_atom_Init( "iTunEXTC", media_rating, &rDNS_data_flags, "com.apple.iTunes"); APar_Unified_atom_Put(rDNS_rating_data_atom, media_rating, UTF8_iTunesStyle_Unlimited, 0, 0); } break; } case Meta_ID3v2Tag: { const char *target_frame_ID = NULL; uint16_t packed_lang = 0; uint8_t char_encoding = TE_UTF8; // utf8 is the default encoding char meta_container = 0 - MOVIE_LEVEL_ATOM; bool multistring = false; APar_ScanAtoms(ISObasemediafile); // limit the files that can be tagged with meta.ID32 atoms. The file has // to conform to the ISO BMFFv2 in order for a 'meta' atom. This should // exclude files branded as 3gp5 for example, except it doesn't always. // The test is for a compatible brand (of a v2 ISO MBFF). Quicktime writes // some 3GPP files as 3gp5 with a compatible brand of mp42, so tagging // works on these files. Not when you use timed text though. if (!APar_assert(parsedAtoms[0].ancillary_data != 0 || (metadata_style >= THIRD_GEN_PARTNER_VER1_REL7 && metadata_style < MOTIONJPEG2000), 3, NULL)) { break; } AdjunctArgs *id3args = (AdjunctArgs *)malloc(sizeof(AdjunctArgs)); id3args->targetLang = NULL; // it will default later to "eng" id3args->descripArg = NULL; id3args->mimeArg = NULL; id3args->pictypeArg = NULL; id3args->ratingArg = NULL; id3args->dataArg = NULL; id3args->pictype_uint8 = 0; id3args->groupSymbol = 0; id3args->zlibCompressed = false; id3args->multistringtext = false; target_frame_ID = ConvertCLIFrameStr_TO_frameID(optarg); if (target_frame_ID == NULL) { target_frame_ID = optarg; } int frameType = FrameStr_TO_FrameType(target_frame_ID); if (frameType >= 0) { if (TestCLI_for_FrameParams(frameType, 0)) { id3args->descripArg = find_ID3_optarg(argv, optind, "desc="); } if (TestCLI_for_FrameParams(frameType, 1)) { id3args->mimeArg = find_ID3_optarg(argv, optind, "mimetype="); } if (TestCLI_for_FrameParams(frameType, 2)) { id3args->pictypeArg = find_ID3_optarg(argv, optind, "imagetype="); } if (TestCLI_for_FrameParams(frameType, 3)) { id3args->dataArg = find_ID3_optarg(argv, optind, "uniqueID="); } if (TestCLI_for_FrameParams(frameType, 4)) { id3args->filenameArg = find_ID3_optarg(argv, optind, "filename="); } if (TestCLI_for_FrameParams(frameType, 5)) { id3args->ratingArg = find_ID3_optarg(argv, optind, "rating="); } if (TestCLI_for_FrameParams(frameType, 6)) { id3args->dataArg = find_ID3_optarg(argv, optind, "counter="); } if (TestCLI_for_FrameParams(frameType, 7)) { id3args->dataArg = find_ID3_optarg(argv, optind, "data="); } if (TestCLI_for_FrameParams(frameType, 8)) { id3args->dataArg = find_ID3_optarg(argv, optind, "data="); } if (*find_ID3_optarg(argv, optind, "compressed") == '1') { id3args->zlibCompressed = true; } const char *groupsymbol = find_ID3_optarg(argv, optind, "groupsymbol="); if (groupsymbol[0] == '0' && groupsymbol[1] == 'x') { id3args->groupSymbol = strtoul(groupsymbol, NULL, 16); if (id3args->groupSymbol < 0x80 || id3args->groupSymbol > 0xF0) id3args->groupSymbol = 0; } } scan_ID3_optargs(argv, optind, id3args->targetLang, packed_lang, char_encoding, &meta_container, multistring); if (id3args->targetLang == NULL) id3args->targetLang = "eng"; APar_OpenISOBaseMediaFile( ISObasemediafile, true); // if not already scanned, the whole tag for // *this* ID32 atom needs to be read from file AtomicInfo *id32_atom = APar_ID32_atom_Init( target_frame_ID, meta_container, id3args->targetLang, packed_lang); if (strcmp(argv[optind + 0], "extract") == 0 && (strcmp(target_frame_ID, "APIC") == 0 || strcmp(target_frame_ID, "GEOB") == 0)) { if (id32_atom != NULL) { APar_Extract_ID3v2_file( id32_atom, target_frame_ID, ISObasemediafile, NULL, id3args); APar_OpenISOBaseMediaFile(ISObasemediafile, false); } exit(0); } APar_OpenISOBaseMediaFile(ISObasemediafile, false); APar_ID3FrameAmmend( id32_atom, target_frame_ID, argv[optind + 0], id3args, char_encoding); free(id3args); id3args = NULL; break; } // utility functions case Metadata_Purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta.meta.ilst", SIMPLE_ATOM, 0); break; } case UserData_Purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta", SIMPLE_ATOM, 0); break; } case foobar_purge: { APar_ScanAtoms(ISObasemediafile); APar_RemoveAtom("moov.udta.tags", UNKNOWN_ATOM, 0); break; } case Opt_FreeFree: { APar_ScanAtoms(ISObasemediafile); int free_level = -1; if (argv[optind]) { sscanf(argv[optind], "%i", &free_level); } APar_freefree(free_level); break; } case OPT_OverWrite: { alter_original = true; break; } #if defined(_WIN32) case OPT_PreserveTimeStamps: { alter_original = true; preserve_timestamps = true; break; } #endif case Meta_dump: { APar_ScanAtoms(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_MetadataFileDump(ISObasemediafile); APar_OpenISOBaseMediaFile(ISObasemediafile, false); APar_FreeMemory(); exit(0); } case OPT_NoOptimize: { force_existing_hierarchy = true; break; } case OPT_OutputFile: { output_file = optarg; break; } case Meta_movementCount: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementCount")) { break; } uint8_t data_value = 0; sscanf(optarg, "%" SCNu8, &data_value); AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvc.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 8); break; } case Meta_movementName: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementName")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvn.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_movementNumber: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "movementNumber")) { break; } uint8_t data_value = 0; sscanf(optarg, "%" SCNu8, &data_value); AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251mvi.data", optarg, AtomFlags_Data_UInt); APar_Unified_atom_Put( atom, NULL, UTF8_iTunesStyle_256glyphLimited, data_value, 8); break; } case Meta_showWorkMovement: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "showWorkMovement")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.shwm.data", optarg, AtomFlags_Data_Binary); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } case Meta_work: { APar_ScanAtoms(ISObasemediafile); if (!APar_assert(metadata_style == ITUNES_STYLE, 1, "work")) { break; } AtomicInfo *atom = APar_MetaData_atom_Init( "moov.udta.meta.ilst.\251wrk.data", optarg, AtomFlags_Data_Text); APar_Unified_atom_Put( atom, optarg, UTF8_iTunesStyle_256glyphLimited, 0, 0); break; } } /* end switch */ } /* end while */ // after all the modifications are enacted on the tree in memory, THEN // write out the changes if (modified_atoms) { #if defined(_WIN32) HANDLE hFile, hFileOut; FILETIME createTime, accessTime, writeTime; if (preserve_timestamps == true) { hFile = APar_OpenFileWin32(ISObasemediafile, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) { GetFileTime(hFile, &createTime, &accessTime, &writeTime); CloseHandle(hFile); } else { fprintf(stdout, "\n Invalid HANDLE!"); } } #endif APar_DetermineAtomLengths(); APar_OpenISOBaseMediaFile(ISObasemediafile, true); APar_WriteFile(ISObasemediafile, output_file, alter_original); #if defined(_WIN32) if (preserve_timestamps == true) { hFileOut = APar_OpenFileWin32(ISObasemediafile, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (hFileOut != INVALID_HANDLE_VALUE) { SetFileTime(hFileOut, &createTime, &accessTime, &writeTime); CloseHandle(hFileOut); } } #endif if (!alter_original) { // The file was opened orignally as read-only; when it came time to // writeback into the original file, that FILE was closed, and a // new one opened with write abilities, so to close a FILE that no // longer exists would.... be retarded. APar_OpenISOBaseMediaFile(ISObasemediafile, false); } } else { if (ISObasemediafile != NULL && argc > 3 && !deep_atom_scan) { fprintf(stdout, "No changes.\n"); } } APar_FreeMemory(); return 0; } #if defined(_WIN32) #if !defined(__CYGWIN__) int wmain(int argc, wchar_t *arguments[]) { int return_val = 0; uint16_t name_len = wcslen(arguments[0]); if (name_len >= 9 && _wcsicmp(arguments[0] + (name_len - 9), L"-utf8.exe") == 0) { UnicodeOutputStatus = UNIVERSAL_UTF8; } else { UnicodeOutputStatus = WIN32_UTF16; } char **argv = (char **)calloc(argc + 1, sizeof(char *)); // for native Win32 & full unicode support (well, cli arguments anyway), // take whatever 16bit unicode windows gives (utf16le), and convert // EVERYTHING that is sends to utf8; use those utf8 strings (mercifully // subject to familiar standby's like strlen) to pass around the program // like getopt_long to get cli options; convert from utf8 filenames as // required for unicode filename support on Windows using wide file // functions. for (int z = 0; z < argc; z++) { uint32_t wchar_length = wcslen(arguments[z]) + 1; argv[z] = (char *)malloc(sizeof(char) * 8 * wchar_length); memset(argv[z], 0, 8 * wchar_length); if (UnicodeOutputStatus == WIN32_UTF16) { UTF16LEToUTF8((unsigned char *)argv[z], 8 * wchar_length, (unsigned char *)arguments[z], wchar_length * 2); } else { strip_bogusUTF16toRawUTF8((unsigned char *)argv[z], 8 * wchar_length, arguments[z], wchar_length); } } argv[argc] = NULL; return_val = real_main(argc, argv); for (int free_cnt = 0; free_cnt < argc; free_cnt++) { free(argv[free_cnt]); argv[free_cnt] = NULL; } free(argv); argv = NULL; return return_val; } #ifdef __MINGW32__ int main() { int argc; wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc); return wmain(argc, argv); } #endif #else // defined __CYGWIN__ int main(int argc, char *argv[]) { size_t name_len = strlen(argv[0]); if (name_len >= 5 && (strcmp(argv[0] + (name_len - 5), "-utf8") == 0 || strcmp(argv[0] + (name_len - 5), "-UTF8") == 0)) { UnicodeOutputStatus = UNIVERSAL_UTF8; } else { UnicodeOutputStatus = WIN32_UTF16; } return real_main(argc, argv); } #endif #else int main(int argc, char *argv[]) { return real_main(argc, argv); } #endif /* vim:ts=4:sw=4:et: */