Overview

Classes

  • _zp_captcha
  • _zp_HTML_cache
  • admin_approval
  • Album
  • AlbumBase
  • AlbumZip
  • AMFReader
  • AMFStream
  • AnyFile
  • AnyFile_Options
  • auto_backup
  • AVCSequenceParameterSetReader
  • bxslider
  • cacheManager
  • cachemanager_internal_deprecations
  • cacheManagerFeed
  • CI_jsmin
  • CI_load
  • cloneZenphoto
  • codeIgniter_kludge
  • colorbox
  • Combi
  • Comment
  • comment_form
  • contactformOptions
  • cookieConsent
  • crop_image
  • cycle
  • defaultCodeblocks
  • deprecated_functions
  • DownloadList
  • dynamic_locale
  • dynamicAlbum
  • elFinder
  • elFinder_options
  • elFinderConnector
  • elFinderEditor
  • elFinderEditorOnlineConvert
  • elFinderEditorZipArchive
  • elFinderEditorZohoOffice
  • elFinderLibGdBmp
  • elFinderPlugin
  • elFinderPluginAutoResize
  • elFinderPluginAutoRotate
  • elFinderPluginNormalizer
  • elFinderPluginSanitizer
  • elFinderPluginWatermark
  • elFinderSession
  • elFinderVolumeBox
  • elFinderVolumeDriver
  • elFinderVolumeDropbox
  • elFinderVolumeDropbox2
  • elFinderVolumeFlysystemGoogleDriveCache
  • elFinderVolumeFlysystemGoogleDriveNetmount
  • elFinderVolumeFTP
  • elFinderVolumeGoogleDrive
  • elFinderVolumeGroup
  • elFinderVolumeLocalFileSystem
  • elFinderVolumeMySQL
  • elFinderVolumeOneDrive
  • elFinderVolumeTrash
  • elFinderVolumeTrashMySQL
  • email_new_user
  • exampleMacros
  • external_auth
  • favorites
  • favoritesOptions
  • feed
  • fieldExtender
  • flag_thumbnail
  • Gallery
  • galleryArticles
  • getID3
  • getid3_aac
  • getid3_apetag
  • getid3_flv
  • getid3_handler
  • getid3_id3v1
  • getid3_id3v2
  • getid3_lib
  • getid3_lyrics3
  • getid3_mp3
  • getid3_mpeg
  • getid3_quicktime
  • getid3_swf
  • GoogleMap
  • Googlemaps
  • hitcounter
  • htmlmetatags
  • Image
  • internal_deprecations
  • ipBlocker
  • jPlayer
  • jplayer_options
  • jquery_rating
  • JSMin
  • lazyload
  • lib_GD_Options
  • lib_Imagick_Options
  • lib_NoGraphics
  • matomoStats
  • MediaObject
  • menu_manager
  • MergedRSS
  • MergedRSSOptions
  • mobile
  • Mobile_Detect
  • mobileTheme
  • multipleLayoutOptions
  • null_seo
  • OAuthConsumer
  • OAuthDataStore
  • OAuthRequest
  • OAuthServer
  • OAuthSignatureMethod
  • OAuthSignatureMethod_HMAC_SHA1
  • OAuthSignatureMethod_PLAINTEXT
  • OAuthSignatureMethod_RSA_SHA1
  • OAuthToken
  • OAuthUtil
  • openStreetMap
  • openStreetMapOptions
  • pagedThumbsNav
  • pagedthumbsOptions
  • PersistentObject
  • PHPMailer\PHPMailer\PHPMailer
  • PHPMailer\PHPMailer\POP3
  • PHPMailer\PHPMailer\SMTP
  • print_album_menu
  • pseudoPlayer
  • publishContent
  • quota_manager
  • reCaptcha
  • RecursiveCallbackFilterIterator
  • redirector
  • redirectorOptions
  • register_user
  • rewriteRules
  • rewriteTokens
  • RSS
  • rss_options
  • scriptlessSocialsharing
  • scriptlessSocialsharingOptions
  • search_statistics
  • SearchEngine
  • security_logger
  • securityHeaders
  • securityheadersOptions
  • seo_locale
  • Services_JSON
  • Services_JSON_Error
  • setup
  • setupMutex
  • setupRSS
  • show_not_loggedin
  • sitemap
  • sitemapOptions
  • static_html_cache
  • staticHTMLCacheOptions
  • tagsuggest
  • TextObject
  • TextObject_Options
  • ThemeObject
  • themeSwitcher
  • tinymce4Options
  • tinyURL
  • Transientimage
  • UploadHandler
  • user_expiry
  • user_groups
  • user_logout_options
  • userAddressFields
  • userDataExport
  • utf8
  • Video
  • VideoObject_Options
  • WEBdocs
  • WEBdocs_Options
  • xmpMetadata
  • Zenpage
  • Zenpage_internal_deprecations
  • ZenpageCategory
  • zenpagecms
  • ZenpageItems
  • ZenpageNews
  • ZenpagePage
  • ZenpageRoot
  • Zenphoto_Administrator
  • Zenphoto_Authority
  • zenphoto_org_news
  • zenphoto_seo
  • zenphotoDonate
  • ZipStream
  • zp_PHPMailer
  • zpFunctions
  • zpMutex
  • zpSimpleSpam
  • zpTrivialSpam

Interfaces

  • elFinderSessionInterface

Exceptions

  • elFinderAbortException
  • elFinderTriggerException
  • getid3_exception
  • JSMin_UnterminatedCommentException
  • JSMin_UnterminatedRegExpException
  • JSMin_UnterminatedStringException
  • OAuthExcept
  • PHPMailer\PHPMailer\Exception

Functions

  • access
  • accessAlbums
  • accessAllAlbums
  • accessImage
  • add_context
  • addalbumsToDatabase
  • addCategoriesToDatabase
  • addDateToTitlelink
  • addGeoCoord
  • addItem
  • addMissingDefaultRewriteTokens
  • addPagesToDatabase
  • addReconfigureNote
  • addSubalbumMenus
  • addWatermark
  • admin_album_list
  • admin_securityChecks
  • admin_showupdate
  • adminPageNav
  • adminToolbox
  • albumNumber
  • applyMacros
  • authorSelector
  • bind_textdomain_codeset
  • bindtextdomain
  • build_query
  • build_url
  • bulkActionRedirect
  • bulkTags
  • byteConvert
  • cacheImage
  • checkAccess
  • checkAlbumForImages
  • checkAlbumimagesort
  • checkAlbumParentid
  • checkAlbumPassword
  • checkChosenItemStatus
  • checkChosenMenuset
  • checked
  • checkFolder
  • checkForEmptyTitle
  • checkForGuest
  • checkForPage
  • checkForUpdate
  • checkHitcounterDisplay
  • checkIfChecked
  • checkIfLockedNews
  • checkIfLockedPage
  • checkIfNew
  • checkInstall
  • checkLayoutUseForImages
  • checkObjectsThumb
  • checkPageValidity
  • checkParentLayouts
  • checkPublishDates
  • checkRequiredField
  • checkSchedulePublishingNotes
  • checkSelectedAlbum
  • checkSignature
  • checkTitlelinkDuplicate
  • cleanAlbum
  • cleanHTML
  • clonedFrom
  • codeblocktabsJS
  • comment_form_addComment
  • comment_form_handle_comment
  • comment_form_PaginationJS
  • comment_form_postcomment
  • comment_form_print10Most
  • comment_form_visualEditor
  • commentFormUseCaptcha
  • commentReply
  • commentsAllowed
  • compressRow
  • consolidatedEditMessages
  • copyLayoutSelection
  • copyThemeDirectory
  • createMenuIfNotExists
  • createRelatedItemsResultArray
  • createTitlelink
  • cron_starter
  • curlRequest
  • currentRelativeURL
  • customOptions
  • dateDiff
  • datepickerJS
  • dateTimeConvert
  • db_affected_rows
  • db_close
  • db_collation
  • db_connect
  • db_count
  • db_create
  • db_create_table
  • db_error
  • db_fetch_assoc
  • db_fetch_row
  • db_free_result
  • db_getClientInfo
  • db_getServerInfo
  • db_getSQLmode
  • db_getVersion
  • db_insert_id
  • db_isMariaDB
  • db_LIKE_escape
  • db_list_fields
  • db_name
  • db_num_rows
  • db_permissions
  • db_quote
  • db_setSQLmode
  • db_show
  • db_software
  • db_table_update
  • db_truncate_table
  • debug404
  • debugLog
  • debugLogBacktrace
  • debuglogReconfigureNote
  • debugLogVar
  • decompressField
  • decompressRow
  • defaultCodeblocks_codebox
  • deleteArticle
  • deleteCategory
  • deleteItem
  • deleteLayoutSelection
  • deletePage
  • deleteThemeDirectory
  • dircopy
  • disableExtension
  • elFinder_admin_tabs
  • elFinder_tinymce
  • elFinderAutoloader
  • enableExtension
  • executeRSS
  • exitZP
  • exposeZenPhotoInformations
  • extendExecution
  • extensionEnabled
  • fetchComments
  • filesystemToInternal
  • fillbuffer
  • filterImageQuery
  • fix_path_redirect
  • formatList
  • fullText
  • galleryAlbumsPerPage
  • gallerystats_filesize_r
  • genAlbumList
  • generateAttributesFromArray
  • generateImageCacheFile
  • generateLanguageList
  • generateListFromArray
  • generateListFromFiles
  • generateRadiobuttonsFromArray
  • generateUnorderedListFromArray
  • get_AnyFile_suffixes
  • get_context
  • get_filterScript
  • get_instance
  • get_language_string
  • getAdminstratorsOptionsArray
  • getAdminThumb
  • getAdminThumbHTML
  • getAlbumArray
  • getAlbumBreadcrumb
  • getAlbumBreadcrumbAdmin
  • getAlbumCustomData
  • getAlbumData
  • getAlbumDate
  • getAlbumDesc
  • getAlbumFolder
  • getAlbumGeodata
  • getAlbumInherited
  • getAlbumLocation
  • getAlbumPage
  • getAlbumStatistic
  • getAlbumThumb
  • getAlbumTitle
  • getAlbumURL
  • getAllAccessibleAlbums
  • getAllAlbums
  • getAllDates
  • getAllowedTags
  • getAllSubAlbumIDs
  • getAllTagsCount
  • getAllTagsFromAlbum
  • getAllTagsFromAlbum_multi_unique
  • getAllTagsFromZenpage
  • getAllTagsUnique
  • getAllTranslations
  • getAnnotatedAlbumTitle
  • getAnnotatedImageTitle
  • getAnonymIP
  • getAuthor
  • getBare
  • getBareAlbumDesc
  • getBareAlbumTitle
  • getBareGalleryDesc
  • getBareGalleryTitle
  • getBareImageDesc
  • getBareImageTitle
  • getBareNewsTitle
  • getBarePageTitle
  • getCheckboxState
  • getCodeblock
  • getCommentAddress
  • getCommentAuthorEmail
  • getCommentAuthorLink
  • getCommentAuthorName
  • getCommentAuthorSite
  • getCommentBody
  • getCommentCount
  • getCommentDateTime
  • getCommentErrors
  • getCommentStored
  • getContactFormMacros
  • getContentShorten
  • getCookieInfoData
  • getCookieInfoHTML
  • getCookieInfoMacro
  • getCurrentMenuItem
  • getCurrentNewsArchive
  • getCurrentPage
  • getCurrentTheme
  • getCustomAlbumThumb
  • getCustomAlbumThumbMaxSpace
  • getCustomGalleryIndexPage
  • getCustomGalleryIndexURL
  • getCustomImageURL
  • getCustomPageURL
  • getCustomSizedImageMaxSpace
  • getCustomSizedImageThumbMaxSpace
  • getDataUsageNotice
  • getDefaultHeight
  • getDefaultRewriteTokens
  • getDefaultSizedImage
  • getDefaultWidth
  • getdownloadList
  • getDownloadURL
  • getE
  • getEnabledPlugins
  • getExpiryDatePost
  • getFavoritesURL
  • getField
  • getFullHeight
  • getFullImageFilesize
  • getFullImageURL
  • getFullWidth
  • getGalleryDesc
  • getGalleryIndexURL
  • getGalleryTitle
  • getGeoCoord
  • getHeadTitle
  • getHitcounter
  • getImageArgs
  • getImageCacheFilename
  • getImageCachePostfix
  • getImageCity
  • getImageCountry
  • getImageCustomData
  • getImageData
  • getImageDate
  • getImageDesc
  • getImageGeodata
  • getImageLocation
  • getImageMetaData
  • getImageParameters
  • getImageProcessorURI
  • getImageProcessorURIFromCacheName
  • getImageRotation
  • getImageState
  • getImageStatistic
  • getImageThumb
  • getImageTitle
  • getImageType
  • getImageURI
  • getImageURL
  • getItem
  • getItemByID
  • getItemTitleAndURL
  • getjPlayerSkinCSS
  • getjPlayerSkins
  • getLangAttributeLocale
  • getLanguageArray
  • getLanguageDisplayName
  • getLanguageFlag
  • getLanguageSubdomains
  • getLanguageText
  • getLatestComments
  • getLatestNews
  • getLatestZenpageComments
  • getLayout
  • getLayoutSelector
  • getLinkHTML
  • getLogTabs
  • getMacros
  • getMainSiteName
  • getMainSiteURL
  • getManagedAlbumList
  • getMaxSpaceContainer
  • getMenuFromLink
  • getMenuItemChilds
  • getMenuItems
  • getMenumanagerPredicessor
  • getMenumanagerSuccessor
  • getMenuSetSelector
  • getMenuVisibility
  • getMimeString
  • getNestedAlbumList
  • getNewsAdminOption
  • getNewsAdminOptionPath
  • getNewsArchivePath
  • getNewsArchiveURL
  • getNewsAuthor
  • getNewsCategories
  • getNewsCategoryCustomData
  • getNewsCategoryDesc
  • getNewsCategoryURL
  • getNewsContent
  • getNewsCustomData
  • getNewsDate
  • getNewsExtraContent
  • getNewsID
  • getNewsIndexURL
  • getNewsPagesStatistic
  • getNewsPathNav
  • getNewsReadMore
  • getNewsTitle
  • getNewsURL
  • getNextAlbum
  • getNextAlbumURL
  • getNextImageThumb
  • getNextImageURL
  • getNextNewsPageURL
  • getNextNewsURL
  • getNextPageURL
  • getNextPrevNews
  • getNotViewableAlbums
  • getNotViewableImages
  • getNumAlbums
  • getNumAllSubalbums
  • getNumImages
  • getNumNews
  • getNumPages
  • getOption
  • getOptionFromDB
  • getOptionList
  • getOwnerAuthor
  • getOwnerAuthorURL
  • getPageAuthor
  • getPageContent
  • getPageCustomData
  • getPageDate
  • getPageExtraContent
  • getPageID
  • getPageLastChangeDate
  • getPageNavList
  • getPageNumURL
  • getPageParentID
  • getPageRedirect
  • getPageSelector
  • getPageSortorder
  • getPageTitle
  • getPageTitleLink
  • getPageURL
  • getParentAlbums
  • getParentAlbumsAdmin
  • getParentBreadcrumb
  • getParentMenuItems
  • getPasswordProtectImage
  • getPHPFiles
  • getPictureOfTheDay
  • getPlugin
  • getPluginFiles
  • getPluginTabs
  • getPrevAlbum
  • getPrevAlbumURL
  • getPrevImageThumb
  • getPrevImageURL
  • getPrevNewsPageURL
  • getPrevNewsURL
  • getPrevPageURL
  • getProtectedImageURL
  • getRandomImages
  • getRandomImagesAlbum
  • getRating
  • getReconfigureNote
  • getRelatedItems
  • getRequestURI
  • getrow
  • getRSSLink
  • getRules
  • getSearchDate
  • getSearchURL
  • getSearchWords
  • getSelectedLayout
  • getSerializedArray
  • getSetClause
  • getSiteHomeURL
  • getSizeCustomImage
  • getSizeDefaultImage
  • getSizeDefaultThumb
  • getSizedImageURL
  • getSizeFullImage
  • getSortByOptions
  • getSortByStatusOptions
  • getStandardGalleryIndexURL
  • getSubtabs
  • getSuffix
  • getSystemLocales
  • getTagCountByAccess
  • getTagOrder
  • getTags
  • gettext
  • gettext_pl
  • gettext_th
  • getThemeFiles
  • getThemeOption
  • getTimezones
  • getTinyMCE4ConfigFiles
  • getTitle
  • getTotalHitcounter
  • getTotalImagesIn
  • getTotalNewsPages
  • getTotalPages
  • getUnprotectedImageURL
  • getUrAlbum
  • getUserIP
  • getUserLocale
  • getUserURL
  • getVersion
  • getWatermarkParam
  • getWatermarkPath
  • getWatermarks
  • getWhereClause
  • getXSRFToken
  • getZenpagePagesOptionsArray
  • getZenpageStatistic
  • handleSearchParms
  • hasDynamicAlbumSuffix
  • hasNextImage
  • hasNextNewsPage
  • hasNextPage
  • hasPrevImage
  • hasPrevNewsPage
  • hasPrevPage
  • hasPrimaryScripts
  • hl_attrval
  • hl_bal
  • hl_cmtcd
  • hl_ent
  • hl_prot
  • hl_regex
  • hl_spec
  • hl_tag
  • hl_tag2
  • hl_tidy
  • hl_version
  • html_decode
  • html_encode
  • html_encodeTagged
  • html_pathurlencode
  • htmLawed
  • httpsRedirect
  • httpUploadHandler
  • httpUploadHandler_admin_tabs
  • i18nSetLocale
  • ignoreSetupRunRequest
  • imageBlurGD
  • imagecreatefrombmp
  • imageDebug
  • imageError
  • imageNumber
  • imgSrcURI
  • in_context
  • installSignature
  • instrument
  • internalToFilesystem
  • inventMenuItem
  • iptc_make_tag
  • is_AdminEditPage
  • is_connected
  • is_News
  • is_NewsArchive
  • is_NewsArticle
  • is_NewsCategory
  • is_NewsPage
  • is_Pages
  • is_valid_email_zp
  • is_zip
  • isAlbumClass
  • isAlbumPage
  • isArchive
  • isCurrentitemParent
  • isHandledAlbum
  • isImageClass
  • isImagePage
  • isImagePhoto
  • isImageVideo
  • isIncompatibleExtension
  • isLandscape
  • isolate
  • isSetupProtected
  • isValidEmail
  • isValidURL
  • jQueryUpload_head
  • jQueryUpload_headers
  • jQueryUploadHandler
  • jQueryUploadHandler_admin_tabs
  • js_encode
  • json_decode
  • json_encode
  • kses
  • kses_array_lc
  • kses_attr
  • kses_bad_protocol
  • kses_bad_protocol_once
  • kses_bad_protocol_once2
  • kses_check_attr_val
  • kses_decode_entities
  • kses_hair
  • kses_hook
  • kses_html_error
  • kses_js_entities
  • kses_no_null
  • kses_normalize_entities
  • kses_normalize_entities2
  • kses_split
  • kses_split2
  • kses_stripslashes
  • kses_version
  • ksesProcess
  • layoutSelector
  • layoutSelector_album
  • listDBUses
  • listDirectoryFiles
  • listUses
  • load_zenpage_news
  • load_zenpage_pages
  • loadLocalOptions
  • log_message
  • lookupSortKey
  • macro_admin_tabs
  • macroList_show
  • makeAlbumCurrent
  • makeImageCurrent
  • makeSpecialImageName
  • markRelease_button
  • mb_strlen
  • mb_strpos
  • mb_strrpos
  • mb_strtolower
  • mb_strtoupper
  • mb_substr
  • mb_substr_count
  • menu_admin_toolbox_global
  • menu_tabs
  • minDiff
  • mkdir_recursive
  • myts_date
  • newAlbum
  • newImage
  • next_album
  • next_comment
  • next_image
  • next_news
  • next_page
  • ngettext
  • ngettext_pl
  • ngettext_th
  • omsAdditions
  • parse_query
  • parse_size
  • parseAllowedTags
  • parseHttpAcceptLanguage
  • pathurlencode
  • pluginDebug
  • populateManagedObjectsList
  • postAlbumSort
  • postIndexDecode
  • postIndexEncode
  • prefix
  • prepareAlbumPage
  • prepareCustomPage
  • prepareImagePage
  • prepareIndexPage
  • print404status
  • print_language_string_list
  • printAddToFavorites
  • printAdminFooter
  • printAdminHeader
  • printAdminRightsTable
  • printAdminThumb
  • printAlbumBreadcrumb
  • printAlbumButtons
  • printAlbumCustomData
  • printAlbumData
  • printAlbumDate
  • printAlbumDesc
  • printAlbumEditForm
  • printAlbumEditRow
  • printAlbumLegend
  • printAlbumLocation
  • printAlbumMenu
  • printAlbumMenuJump
  • printAlbumMenuList
  • printAlbumMenuListAlbum
  • printAlbumsSelector
  • printAlbumStatistic
  • printAlbumStatisticItem
  • printAlbumThumbImage
  • printAlbumTitle
  • printAlbumURL
  • printAllDates
  • printAllNestedList
  • printAllNewsCategories
  • printAllTags
  • printAllTagsAs
  • printAllTagsFromAlbum
  • printAllTagsFromZenpage
  • printAnnotatedAlbumTitle
  • printAnnotatedImageTitle
  • printArticleCategories
  • printArticleDatesDropdown
  • printArticlesPerPageDropdown
  • printAuthorDropdown
  • printBareAlbumDesc
  • printBareAlbumTitle
  • printBareGalleryDesc
  • printBareGalleryTitle
  • printBareImageDesc
  • printBareImageTitle
  • printBareNewsTitle
  • printBarePageTitle
  • printBarGraph
  • printBulkActions
  • printCategoriesStatistic
  • printCategoryCheckboxListEntry
  • printCategoryDropdown
  • printCategoryListSortableTable
  • printCategorySelection
  • printCodeblock
  • printCodeblockEdit
  • printCommentAuthorLink
  • printCommentErrors
  • printCommentForm
  • printContactForm
  • printContactFormMacro
  • printCookieInfo
  • printCopyrightNotice
  • printCurrentNewsArchive
  • printCurrentNewsCategory
  • printCustomAlbumThumbImage
  • printCustomAlbumThumbMaxSpace
  • printCustomMenu
  • printCustomPageSelector
  • printCustomPageURL
  • printCustomSizedImage
  • printCustomSizedImageMaxSpace
  • printCustomSizedImageThumbMaxSpace
  • printDataUsageNotice
  • printDefaultSizedImage
  • printDownloadAlbumZipURL
  • printdownloadList
  • printDownloadURL
  • printEditCommentLink
  • printEditDropdown
  • printExpired
  • printFavoritesURL
  • printFullAlbumsList
  • printFullImageDownloadURL
  • printGalleryDesc
  • printGalleryIndexURL
  • printGalleryTitle
  • printGoogleMap
  • printHeadTitle
  • printHomeLink
  • printImageCustomData
  • printImageData
  • printImageDate
  • printImageDesc
  • printImageMetadata
  • printImageslist
  • printImageStatistic
  • printImageThumb
  • printImageTitle
  • printImageURL
  • printItemEditLink
  • printItemsList
  • printItemsListTable
  • printItemStatusDropdown
  • printjPlayerPlaylist
  • printLangAttribute
  • printLanguageSelector
  • printLastChangeInfo
  • printLatestAlbums
  • printLatestComments
  • printLatestImages
  • printLatestImagesByDate
  • printLatestImagesByMtime
  • printLatestNews
  • printLatestUpdatedAlbums
  • printLinkHTML
  • printLogoAndLinks
  • printManagedObjects
  • printMenuemanagerPageList
  • printMenuemanagerPageListWithNav
  • printMenumanagerBreadcrumb
  • printMenumanagerNextLink
  • printMenumanagerPrevLink
  • printMostPopularItems
  • printMostRatedAlbums
  • printMostRatedImages
  • printMostRatedItems
  • printNestedAlbumsList
  • printNestedItemsList
  • printNestedMenu
  • printNews
  • printNewsArchive
  • printNewsArticlesList
  • printNewsAuthor
  • printNewsCategories
  • printNewsCategoryCustomData
  • printNewsCategoryDesc
  • printNewsCategoryURL
  • printNewsContent
  • printNewsCustomData
  • printNewsDate
  • printNewsExtraContent
  • printNewsIndexURL
  • printNewsPageList
  • printNewsPageListWithNav
  • printNewsStatistic
  • printNewsTitle
  • printNewsURL
  • printNextNewsLink
  • printNextNewsPageLink
  • printNextPageURL
  • printOpenStreetMap
  • printOwnerAuthor
  • printOwnerAuthorURL
  • printPageArticleTags
  • printPageAuthor
  • printPageContent
  • printPageCustomData
  • printPageDate
  • printPagedThumbsNav
  • printPageExtraContent
  • printPageID
  • printPageLastChangeDate
  • printPageList
  • printPageListWithNav
  • printPageMenu
  • printPageNav
  • printPageSelector
  • printPagesListTable
  • printPagesStatistic
  • printPageTitle
  • printPageTitleLink
  • printPageURL
  • printParentBreadcrumb
  • printPasswordForm
  • printPopularAlbums
  • printPopularImages
  • printPrevNewsLink
  • printPrevNewsPageLink
  • printPrevPageURL
  • printPrivacyPageLink
  • printPublished
  • printPublishIconLink
  • printPublishIconLinkGallery
  • printRandomImages
  • printRating
  • printReconfigureError
  • printReconfigureNote
  • printRegisterURL
  • printRegistrationForm
  • printRelatedItems
  • printRSSHeaderLink
  • printRSSLink
  • printScheduledPublishingNotes
  • printSearchBreadcrumb
  • printSearchForm
  • printSelectorWithCustomField
  • printSiteHomeURL
  • printSizedImageURL
  • printSlideShow
  • printSlideShowLink
  • printSortableHead
  • printSortOrderDropdown
  • printSubLevelAlbums
  • printSubPagesExcerpts
  • printSubtabs
  • printTabs
  • printTags
  • printThumbNav
  • printTinyPageNav
  • printTinyZenpageCategorySelector
  • printTopRatedAlbums
  • printTopRatedImages
  • printTopRatedItems
  • printUnpublishedDropdown
  • printUserLogin_out
  • printUserSelector
  • printUserURL
  • printVersion
  • printZenJavascripts
  • printZenpageIconLegend
  • printZenpageItems
  • printZenpageItemsBreadcrumb
  • printZenpageNewsCategorySelector
  • printZenpagePageSelector
  • printZenpagePagesSelector
  • printZenpageStatistic
  • printZenphotoLink
  • process_language_string_save
  • processAlbumBulkActions
  • processAlbumEdit
  • processCodeblockSave
  • processCommentBulkActions
  • processCredentials
  • processCustomOptionSave
  • processEditSelection
  • processExtensionVariable
  • processImageBulkActions
  • processImageEdit
  • processManagedObjects
  • processMenuBulkActions
  • processOrder
  • processRights
  • processTags
  • processZenpageBulkActions
  • propSizes
  • protectSetupFiles
  • publishItem
  • purgeOption
  • purgeThemeOption
  • query
  • query_full_array
  • query_single_row
  • rc4
  • read_exif_data_protected
  • readTags
  • reconfigureAction
  • reconfigureCSS
  • recordMissing
  • redirectionHandler
  • redirectURL
  • rem_context
  • removeDir
  • removeParentAlbumNames
  • removeTrailingSlash
  • renameOption
  • replaceOption
  • replaceThemeOption
  • restore_context
  • reveal
  • rewrite_get_album_image
  • rewrite_path
  • rewriteHandler
  • RSS_Channel
  • RSS_Retrieve
  • RSS_Tags
  • rulesList
  • safe_fnmatch
  • safe_glob
  • sanitize
  • sanitize_numeric
  • sanitize_path
  • sanitize_script
  • sanitize_string
  • sanitizeRedirect
  • save_context
  • saveLayoutSelection
  • saveZenphotoLayoutSelection
  • search_quote
  • secureServer
  • seo_cleanup_button
  • seoFriendly
  • seoFriendlyJS
  • set_context
  • setAlbumSubtabs
  • setexifvars
  • setMainDomain
  • setOption
  • setOptionDefault
  • setSiteState
  • setThemeColumns
  • setThemeOption
  • setThemeOptionDefault
  • setTinyZenpageLocale
  • setupCurrentLocale
  • setupDomain
  • setupTheme
  • shortenContent
  • shortentitle
  • showOrNotShowField
  • showZenphotoOptions
  • shuffle_assoc
  • signatureChange
  • site_upgrade_button
  • site_upgrade_status
  • skipScheduledPublishing
  • sortArray
  • sortByKey
  • sortByMultilingual
  • sortMultiArray
  • standardScripts
  • standardThemeOptions
  • stickyNews
  • storeConfig
  • storeTags
  • stripSuffix
  • submenuOf
  • switchLog
  • tagSelector
  • tagSuggestJS
  • tagSuggestJS_admin
  • tagSuggestJS_frontend
  • tagURLs
  • textdomain
  • themeIsEditable
  • themeSetup
  • tidyHTML
  • timezoneDiff
  • tinymce4ConfigJS
  • truncate_string
  • unprotectSetupFiles
  • unpublishedZenpageItemCheck
  • unpublishedZenphotoItemCheck
  • unpublishSubalbums
  • unQuote
  • unTagURLs
  • unzip
  • updateArticle
  • updateCacheName
  • updateCategory
  • updateConfigItem
  • updateImageProcessorLink
  • updateItemSortorder
  • updateItemsSortorder
  • updateMenuItem
  • updatePage
  • upload_extra
  • upload_form
  • upload_head
  • user_mailing_list_button
  • validateLocale
  • writeHeader
  • XSRFdefender
  • XSRFToken
  • zenpageAlbumImage
  • zenpageBulkActionMessage
  • zenpageJSCSS
  • zenpagePublish
  • zenphoto_PHPMailer
  • zenphoto_sendmail
  • zenPhotoTheme
  • zp_apply_filter
  • zp_clearCookie
  • zp_colorAllocate
  • zp_cookieEncode
  • zp_copyCanvas
  • zp_createImage
  • zp_drawRectangle
  • zp_error
  • zp_filter_slot
  • zp_filter_unique_id
  • zp_getCookie
  • zp_getFonts
  • zp_graphicsLibInfo
  • zp_handle_password
  • zp_handle_password_single
  • zp_has_filter
  • zp_image_types
  • zp_imageCanRotate
  • zp_imageColorTransparent
  • zp_imageDims
  • zp_imageFill
  • zp_imageFontHeight
  • zp_imageFontWidth
  • zp_imageFromString
  • zp_imageGet
  • zp_imageGray
  • zp_imageHeight
  • zp_imageIPTC
  • zp_imageKill
  • zp_imageLoadFont
  • zp_imageMerge
  • zp_imageOutput
  • zp_imageResizeAlpha
  • zp_imageResizeTransparent
  • zp_imageUnsharpMask
  • zp_imageWidth
  • zp_load_album
  • zp_load_gallery
  • zp_load_image
  • zp_load_page
  • zp_load_request
  • zp_load_search
  • zp_loggedin
  • zp_mail
  • zp_register_filter
  • zp_remove_filter
  • zp_resampleImage
  • zp_rotateImage
  • zp_session_destroy
  • zp_session_start
  • zp_setCookie
  • zp_writeString
  • zpErrorHandler
  • zpFormattedDate
  • zpRewriteURL
  • Overview
  • Class
  • Tree
  • Package
  • Deprecated
   1:    2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17:   18:   19:   20:   21:   22:   23:   24:   25:   26:   27:   28:   29:   30:   31:   32:   33:   34:   35:   36:   37:   38:   39:   40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52:   53:   54:   55:   56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66:   67:   68:   69:   70:   71:   72:   73:   74:   75:   76:   77:   78:   79:   80:   81:   82:   83:   84:   85:   86:   87:   88:   89:   90:   91:   92:   93:   94:   95:   96:   97:   98:   99:  100:  101:  102:  103:  104:  105:  106:  107:  108:  109:  110:  111:  112:  113:  114:  115:  116:  117:  118:  119:  120:  121:  122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159:  160:  161:  162:  163:  164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201:  202:  203:  204:  205:  206:  207:  208:  209:  210:  211:  212:  213:  214:  215:  216:  217:  218:  219:  220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232:  233:  234:  235:  236:  237:  238:  239:  240:  241:  242:  243:  244:  245:  246:  247:  248:  249:  250:  251:  252:  253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263:  264:  265:  266:  267:  268:  269:  270:  271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282:  283:  284:  285:  286:  287:  288:  289:  290:  291:  292:  293:  294:  295:  296:  297:  298:  299:  300:  301:  302:  303:  304:  305:  306:  307:  308:  309:  310:  311:  312:  313:  314:  315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330:  331:  332:  333:  334:  335:  336:  337:  338:  339:  340:  341:  342:  343:  344:  345:  346:  347:  348:  349:  350:  351:  352:  353:  354:  355:  356:  357:  358:  359:  360:  361:  362:  363:  364:  365:  366:  367:  368:  369:  370:  371:  372:  373:  374:  375:  376:  377:  378:  379:  380:  381:  382:  383:  384:  385:  386:  387:  388:  389:  390:  391:  392:  393:  394:  395:  396:  397:  398:  399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412:  413:  414:  415:  416:  417:  418:  419:  420:  421:  422:  423:  424:  425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459:  460:  461:  462:  463:  464:  465:  466:  467:  468:  469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479:  480:  481:  482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511:  512:  513:  514:  515:  516:  517:  518:  519:  520:  521:  522:  523:  524:  525:  526:  527:  528:  529:  530:  531:  532:  533:  534:  535:  536:  537:  538:  539:  540:  541:  542:  543:  544:  545:  546:  547:  548:  549:  550:  551:  552:  553:  554:  555:  556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606:  607:  608:  609:  610:  611:  612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637:  638:  639:  640:  641:  642:  643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688:  689:  690:  691:  692:  693:  694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708:  709:  710:  711:  712:  713:  714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725:  726:  727:  728:  729:  730:  731:  732:  733:  734:  735:  736:  737:  738:  739:  740:  741:  742:  743:  744:  745:  746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764:  765:  766:  767:  768:  769:  770:  771:  772:  773:  774:  775:  776:  777:  778:  779:  780:  781:  782:  783:  784:  785:  786:  787:  788:  789:  790:  791:  792:  793:  794:  795:  796:  797:  798:  799:  800:  801:  802:  803:  804:  805:  806:  807:  808:  809:  810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829:  830:  831:  832:  833:  834:  835:  836:  837:  838:  839:  840:  841:  842:  843:  844:  845:  846:  847:  848:  849:  850:  851:  852:  853:  854:  855:  856:  857:  858:  859:  860:  861:  862:  863:  864:  865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885:  886:  887:  888:  889:  890:  891:  892:  893:  894:  895:  896:  897:  898:  899:  900:  901:  902:  903:  904:  905:  906:  907:  908:  909:  910:  911:  912:  913:  914:  915:  916:  917:  918:  919:  920:  921:  922:  923:  924:  925:  926:  927:  928:  929:  930:  931:  932:  933:  934:  935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959:  960:  961:  962:  963:  964:  965:  966:  967:  968:  969:  970:  971:  972:  973:  974:  975:  976:  977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987:  988:  989:  990:  991:  992:  993:  994:  995:  996:  997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234: 1235: 1236: 1237: 1238: 1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290: 1291: 1292: 1293: 1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303: 1304: 1305: 1306: 1307: 1308: 1309: 1310: 1311: 1312: 1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 1350: 1351: 1352: 1353: 1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363: 1364: 1365: 1366: 1367: 1368: 1369: 1370: 1371: 1372: 1373: 1374: 1375: 1376: 1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386: 1387: 1388: 1389: 1390: 1391: 1392: 1393: 1394: 1395: 1396: 1397: 1398: 1399: 1400: 1401: 1402: 1403: 1404: 1405: 1406: 1407: 1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427: 1428: 1429: 1430: 1431: 1432: 1433: 1434: 1435: 1436: 1437: 1438: 1439: 1440: 1441: 1442: 1443: 1444: 1445: 1446: 1447: 1448: 1449: 1450: 1451: 1452: 1453: 1454: 1455: 1456: 1457: 1458: 1459: 1460: 1461: 1462: 1463: 1464: 1465: 1466: 1467: 1468: 1469: 1470: 1471: 1472: 1473: 1474: 1475: 1476: 1477: 1478: 1479: 1480: 1481: 1482: 1483: 1484: 1485: 1486: 1487: 1488: 1489: 1490: 1491: 1492: 1493: 1494: 1495: 1496: 1497: 1498: 1499: 1500: 1501: 1502: 1503: 1504: 1505: 1506: 1507: 1508: 1509: 1510: 1511: 1512: 1513: 1514: 1515: 1516: 1517: 1518: 1519: 1520: 1521: 1522: 1523: 1524: 1525: 1526: 1527: 1528: 1529: 1530: 1531: 1532: 1533: 1534: 1535: 1536: 1537: 1538: 1539: 1540: 1541: 1542: 1543: 1544: 1545: 1546: 1547: 1548: 1549: 1550: 1551: 1552: 1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562: 1563: 1564: 1565: 1566: 1567: 1568: 1569: 1570: 1571: 1572: 1573: 1574: 1575: 1576: 1577: 1578: 1579: 1580: 1581: 1582: 1583: 1584: 1585: 1586: 1587: 1588: 1589: 1590: 1591: 1592: 1593: 1594: 1595: 1596: 1597: 1598: 1599: 1600: 1601: 1602: 1603: 1604: 1605: 1606: 1607: 1608: 1609: 1610: 1611: 1612: 1613: 1614: 1615: 1616: 1617: 1618: 1619: 1620: 1621: 1622: 1623: 1624: 1625: 1626: 1627: 1628: 1629: 1630: 1631: 1632: 1633: 1634: 1635: 1636: 1637: 1638: 1639: 1640: 1641: 1642: 1643: 1644: 1645: 1646: 1647: 1648: 1649: 1650: 1651: 1652: 1653: 1654: 1655: 1656: 1657: 1658: 1659: 1660: 1661: 1662: 1663: 1664: 1665: 1666: 1667: 1668: 1669: 1670: 1671: 1672: 1673: 1674: 1675: 1676: 1677: 1678: 1679: 1680: 1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688: 1689: 1690: 1691: 1692: 1693: 1694: 1695: 1696: 1697: 1698: 1699: 1700: 1701: 1702: 1703: 1704: 1705: 1706: 1707: 1708: 1709: 1710: 1711: 1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720: 1721: 1722: 1723: 1724: 1725: 1726: 1727: 1728: 1729: 1730: 1731: 1732: 1733: 1734: 1735: 1736: 1737: 1738: 1739: 1740: 1741: 1742: 1743: 1744: 1745: 1746: 1747: 1748: 1749: 1750: 1751: 1752: 1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778: 1779: 1780: 1781: 1782: 1783: 1784: 1785: 1786: 1787: 1788: 1789: 1790: 1791: 1792: 1793: 1794: 1795: 1796: 1797: 1798: 1799: 1800: 1801: 1802: 1803: 1804: 1805: 1806: 1807: 1808: 1809: 1810: 1811: 1812: 1813: 1814: 1815: 1816: 1817: 1818: 1819: 1820: 1821: 1822: 1823: 1824: 1825: 1826: 1827: 1828: 1829: 1830: 1831: 1832: 1833: 1834: 1835: 1836: 1837: 1838: 1839: 1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849: 1850: 1851: 1852: 1853: 1854: 1855: 1856: 1857: 1858: 1859: 1860: 1861: 1862: 1863: 1864: 1865: 1866: 1867: 1868: 1869: 1870: 1871: 1872: 1873: 1874: 1875: 1876: 1877: 1878: 1879: 1880: 1881: 1882: 1883: 1884: 1885: 1886: 1887: 1888: 1889: 1890: 1891: 1892: 1893: 1894: 1895: 1896: 1897: 1898: 1899: 1900: 1901: 1902: 1903: 1904: 1905: 1906: 1907: 1908: 1909: 1910: 1911: 1912: 1913: 1914: 1915: 1916: 1917: 1918: 1919: 1920: 1921: 1922: 1923: 1924: 1925: 1926: 1927: 1928: 1929: 1930: 1931: 1932: 1933: 1934: 1935: 1936: 1937: 1938: 1939: 1940: 1941: 1942: 1943: 1944: 1945: 1946: 1947: 1948: 1949: 1950: 1951: 1952: 1953: 1954: 1955: 1956: 1957: 1958: 1959: 1960: 1961: 1962: 1963: 1964: 1965: 1966: 1967: 1968: 1969: 1970: 1971: 1972: 1973: 1974: 1975: 1976: 1977: 1978: 1979: 1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987: 1988: 1989: 1990: 1991: 1992: 1993: 1994: 1995: 1996: 1997: 1998: 1999: 2000: 2001: 2002: 2003: 2004: 2005: 2006: 2007: 2008: 2009: 2010: 2011: 2012: 2013: 2014: 2015: 2016: 2017: 2018: 2019: 2020: 2021: 2022: 2023: 2024: 2025: 2026: 2027: 2028: 2029: 2030: 2031: 2032: 2033: 2034: 2035: 2036: 2037: 2038: 2039: 2040: 2041: 2042: 2043: 2044: 2045: 2046: 2047: 2048: 2049: 2050: 2051: 2052: 2053: 2054: 2055: 2056: 2057: 2058: 2059: 2060: 2061: 2062: 2063: 2064: 2065: 2066: 2067: 2068: 2069: 2070: 2071: 2072: 2073: 2074: 2075: 2076: 2077: 2078: 2079: 2080: 2081: 2082: 2083: 2084: 2085: 2086: 2087: 2088: 2089: 2090: 2091: 2092: 2093: 2094: 2095: 2096: 2097: 2098: 2099: 2100: 2101: 2102: 2103: 2104: 2105: 2106: 2107: 2108: 2109: 2110: 2111: 2112: 2113: 2114: 2115: 2116: 2117: 2118: 2119: 2120: 2121: 2122: 2123: 2124: 2125: 2126: 2127: 2128: 2129: 2130: 2131: 2132: 2133: 2134: 2135: 2136: 2137: 2138: 2139: 2140: 2141: 2142: 2143: 2144: 2145: 2146: 2147: 2148: 2149: 2150: 2151: 2152: 2153: 2154: 2155: 2156: 2157: 2158: 2159: 2160: 2161: 2162: 2163: 2164: 2165: 2166: 2167: 2168: 2169: 2170: 2171: 2172: 2173: 2174: 2175: 2176: 2177: 2178: 2179: 2180: 2181: 2182: 2183: 2184: 2185: 2186: 2187: 2188: 2189: 2190: 2191: 2192: 2193: 2194: 2195: 2196: 2197: 2198: 2199: 2200: 2201: 2202: 2203: 2204: 2205: 2206: 2207: 2208: 2209: 2210: 2211: 2212: 2213: 2214: 2215: 2216: 2217: 2218: 2219: 2220: 2221: 2222: 2223: 2224: 2225: 2226: 2227: 2228: 2229: 2230: 2231: 2232: 2233: 2234: 2235: 2236: 2237: 2238: 2239: 2240: 2241: 2242: 2243: 2244: 2245: 2246: 2247: 2248: 2249: 2250: 2251: 2252: 2253: 2254: 2255: 2256: 2257: 2258: 2259: 2260: 2261: 2262: 2263: 2264: 2265: 2266: 2267: 2268: 2269: 2270: 2271: 2272: 2273: 2274: 2275: 2276: 2277: 2278: 2279: 2280: 2281: 2282: 2283: 2284: 2285: 2286: 2287: 2288: 2289: 2290: 2291: 2292: 2293: 2294: 2295: 2296: 2297: 2298: 2299: 2300: 2301: 2302: 2303: 2304: 2305: 2306: 2307: 2308: 2309: 2310: 2311: 2312: 2313: 2314: 2315: 2316: 2317: 2318: 2319: 2320: 2321: 2322: 2323: 2324: 2325: 2326: 2327: 2328: 2329: 2330: 2331: 2332: 2333: 2334: 2335: 2336: 2337: 2338: 2339: 2340: 2341: 2342: 2343: 2344: 2345: 2346: 2347: 2348: 2349: 2350: 2351: 2352: 2353: 2354: 2355: 2356: 2357: 2358: 2359: 2360: 2361: 2362: 2363: 2364: 2365: 2366: 2367: 2368: 2369: 2370: 2371: 2372: 2373: 2374: 2375: 2376: 2377: 2378: 2379: 2380: 2381: 2382: 2383: 2384: 2385: 2386: 2387: 2388: 2389: 2390: 2391: 2392: 2393: 2394: 2395: 2396: 2397: 2398: 2399: 2400: 2401: 2402: 2403: 2404: 2405: 2406: 2407: 2408: 2409: 2410: 2411: 2412: 2413: 2414: 2415: 2416: 2417: 2418: 2419: 2420: 2421: 2422: 2423: 2424: 2425: 2426: 2427: 2428: 2429: 2430: 2431: 2432: 2433: 2434: 2435: 2436: 2437: 2438: 2439: 2440: 2441: 2442: 2443: 2444: 2445: 2446: 2447: 2448: 2449: 2450: 2451: 2452: 2453: 2454: 2455: 2456: 2457: 2458: 2459: 2460: 2461: 2462: 2463: 2464: 2465: 2466: 2467: 2468: 2469: 2470: 2471: 2472: 2473: 2474: 2475: 2476: 2477: 2478: 2479: 2480: 2481: 2482: 2483: 2484: 2485: 2486: 2487: 2488: 2489: 2490: 2491: 2492: 2493: 2494: 2495: 2496: 2497: 2498: 2499: 2500: 2501: 2502: 2503: 2504: 2505: 2506: 2507: 2508: 2509: 2510: 2511: 2512: 2513: 2514: 2515: 2516: 2517: 2518: 2519: 2520: 2521: 2522: 2523: 2524: 2525: 2526: 2527: 2528: 2529: 2530: 2531: 2532: 2533: 2534: 2535: 2536: 2537: 2538: 2539: 2540: 2541: 2542: 2543: 2544: 2545: 2546: 2547: 2548: 2549: 2550: 2551: 2552: 2553: 2554: 2555: 2556: 2557: 2558: 2559: 2560: 2561: 2562: 2563: 2564: 2565: 2566: 2567: 2568: 2569: 2570: 2571: 2572: 2573: 2574: 2575: 2576: 2577: 2578: 2579: 2580: 2581: 2582: 2583: 2584: 2585: 2586: 2587: 2588: 2589: 2590: 2591: 2592: 2593: 2594: 2595: 2596: 2597: 2598: 2599: 2600: 2601: 2602: 2603: 2604: 2605: 2606: 2607: 2608: 2609: 2610: 2611: 2612: 2613: 2614: 2615: 2616: 2617: 2618: 2619: 2620: 2621: 2622: 2623: 2624: 2625: 2626: 2627: 2628: 2629: 2630: 2631: 2632: 2633: 2634: 2635: 2636: 2637: 2638: 2639: 2640: 2641: 2642: 2643: 2644: 2645: 2646: 2647: 2648: 2649: 2650: 2651: 2652: 2653: 2654: 2655: 2656: 2657: 2658: 2659: 2660: 2661: 2662: 2663: 2664: 2665: 2666: 2667: 2668: 2669: 2670: 2671: 2672: 2673: 2674: 2675: 2676: 2677: 2678: 2679: 2680: 2681: 2682: 2683: 2684: 2685: 2686: 2687: 2688: 2689: 2690: 2691: 2692: 2693: 2694: 2695: 2696: 2697: 2698: 2699: 2700: 2701: 2702: 2703: 2704: 2705: 2706: 2707: 2708: 2709: 2710: 2711: 2712: 2713: 2714: 2715: 2716: 2717: 2718: 2719: 2720: 2721: 2722: 2723: 2724: 2725: 2726: 2727: 2728: 2729: 2730: 2731: 2732: 2733: 2734: 2735: 2736: 2737: 2738: 2739: 2740: 2741: 2742: 2743: 2744: 2745: 2746: 2747: 2748: 2749: 2750: 2751: 2752: 2753: 2754: 2755: 2756: 2757: 2758: 2759: 2760: 2761: 2762: 2763: 2764: 2765: 2766: 2767: 2768: 2769: 2770: 2771: 2772: 2773: 2774: 2775: 2776: 2777: 2778: 2779: 2780: 2781: 2782: 2783: 2784: 2785: 2786: 2787: 2788: 2789: 2790: 2791: 2792: 2793: 2794: 2795: 2796: 2797: 2798: 2799: 2800: 2801: 2802: 2803: 2804: 2805: 2806: 2807: 2808: 2809: 2810: 2811: 2812: 2813: 2814: 2815: 2816: 2817: 2818: 2819: 2820: 2821: 2822: 2823: 2824: 2825: 2826: 2827: 2828: 2829: 2830: 2831: 2832: 2833: 2834: 2835: 2836: 2837: 2838: 2839: 2840: 2841: 2842: 2843: 2844: 2845: 2846: 2847: 2848: 2849: 2850: 2851: 2852: 2853: 2854: 2855: 2856: 2857: 2858: 2859: 2860: 2861: 2862: 2863: 2864: 2865: 2866: 2867: 2868: 2869: 2870: 2871: 2872: 2873: 2874: 2875: 2876: 2877: 2878: 2879: 2880: 2881: 2882: 2883: 2884: 2885: 2886: 2887: 2888: 2889: 2890: 2891: 2892: 2893: 2894: 2895: 2896: 2897: 2898: 2899: 2900: 2901: 2902: 2903: 2904: 2905: 2906: 2907: 2908: 2909: 2910: 2911: 2912: 2913: 2914: 2915: 2916: 2917: 2918: 2919: 2920: 2921: 2922: 2923: 2924: 2925: 2926: 2927: 2928: 2929: 2930: 2931: 2932: 2933: 2934: 2935: 2936: 2937: 2938: 2939: 2940: 2941: 2942: 2943: 2944: 2945: 2946: 2947: 2948: 2949: 2950: 2951: 2952: 2953: 2954: 2955: 2956: 2957: 2958: 2959: 2960: 2961: 2962: 2963: 2964: 2965: 2966: 2967: 2968: 2969: 2970: 2971: 2972: 2973: 2974: 2975: 2976: 2977: 2978: 2979: 2980: 2981: 2982: 2983: 2984: 2985: 2986: 2987: 2988: 2989: 2990: 2991: 2992: 2993: 2994: 2995: 2996: 2997: 2998: 2999: 3000: 3001: 3002: 3003: 3004: 3005: 3006: 3007: 3008: 3009: 3010: 3011: 3012: 3013: 3014: 3015: 3016: 3017: 3018: 3019: 3020: 3021: 3022: 3023: 3024: 3025: 3026: 3027: 3028: 3029: 3030: 3031: 3032: 3033: 3034: 3035: 3036: 3037: 3038: 3039: 3040: 3041: 3042: 3043: 3044: 3045: 3046: 3047: 3048: 3049: 3050: 3051: 3052: 3053: 3054: 3055: 3056: 3057: 3058: 3059: 3060: 3061: 3062: 3063: 3064: 3065: 3066: 3067: 3068: 3069: 3070: 3071: 3072: 3073: 3074: 3075: 3076: 3077: 3078: 3079: 3080: 3081: 3082: 3083: 3084: 3085: 3086: 3087: 3088: 3089: 3090: 3091: 3092: 3093: 3094: 3095: 3096: 3097: 3098: 3099: 3100: 3101: 3102: 3103: 3104: 3105: 3106: 3107: 3108: 3109: 3110: 3111: 3112: 3113: 3114: 3115: 3116: 3117: 3118: 3119: 3120: 3121: 3122: 3123: 3124: 3125: 3126: 3127: 3128: 3129: 3130: 3131: 3132: 3133: 3134: 3135: 3136: 3137: 3138: 3139: 3140: 3141: 3142: 3143: 3144: 3145: 3146: 3147: 3148: 3149: 3150: 3151: 3152: 3153: 3154: 3155: 3156: 3157: 3158: 3159: 3160: 3161: 3162: 3163: 3164: 3165: 3166: 3167: 3168: 3169: 3170: 3171: 3172: 3173: 3174: 3175: 3176: 3177: 3178: 3179: 3180: 3181: 3182: 3183: 3184: 3185: 3186: 3187: 3188: 3189: 3190: 3191: 3192: 3193: 3194: 3195: 3196: 3197: 3198: 3199: 3200: 3201: 3202: 3203: 3204: 3205: 3206: 3207: 3208: 3209: 3210: 3211: 3212: 3213: 3214: 3215: 3216: 3217: 3218: 3219: 3220: 3221: 3222: 3223: 3224: 3225: 3226: 3227: 3228: 3229: 3230: 3231: 3232: 3233: 3234: 3235: 3236: 3237: 3238: 3239: 3240: 3241: 3242: 3243: 3244: 3245: 3246: 3247: 3248: 3249: 3250: 3251: 3252: 3253: 3254: 3255: 3256: 3257: 3258: 3259: 3260: 3261: 3262: 3263: 3264: 3265: 3266: 3267: 3268: 3269: 3270: 3271: 3272: 3273: 3274: 3275: 3276: 3277: 3278: 3279: 3280: 3281: 3282: 3283: 3284: 3285: 3286: 3287: 3288: 3289: 3290: 3291: 3292: 3293: 3294: 3295: 3296: 3297: 3298: 3299: 3300: 3301: 3302: 3303: 3304: 3305: 3306: 3307: 3308: 3309: 3310: 3311: 3312: 3313: 3314: 3315: 3316: 3317: 3318: 3319: 3320: 3321: 3322: 3323: 3324: 3325: 3326: 3327: 3328: 3329: 3330: 3331: 3332: 3333: 3334: 3335: 3336: 3337: 3338: 3339: 3340: 3341: 3342: 3343: 3344: 3345: 3346: 3347: 3348: 3349: 3350: 3351: 3352: 3353: 3354: 3355: 3356: 3357: 3358: 3359: 3360: 3361: 3362: 3363: 3364: 3365: 3366: 3367: 3368: 3369: 3370: 3371: 3372: 3373: 3374: 3375: 3376: 3377: 3378: 3379: 3380: 3381: 3382: 3383: 3384: 3385: 3386: 3387: 3388: 3389: 3390: 3391: 3392: 3393: 3394: 3395: 3396: 3397: 3398: 3399: 3400: 3401: 3402: 3403: 3404: 3405: 3406: 3407: 3408: 3409: 3410: 3411: 3412: 3413: 3414: 3415: 3416: 3417: 3418: 3419: 3420: 3421: 3422: 3423: 3424: 3425: 3426: 3427: 3428: 3429: 3430: 3431: 3432: 3433: 3434: 3435: 3436: 3437: 3438: 3439: 3440: 3441: 3442: 3443: 3444: 3445: 3446: 3447: 3448: 3449: 3450: 3451: 3452: 3453: 3454: 3455: 3456: 3457: 3458: 3459: 3460: 3461: 3462: 3463: 3464: 3465: 3466: 3467: 3468: 3469: 3470: 3471: 3472: 3473: 3474: 3475: 3476: 3477: 3478: 3479: 3480: 3481: 3482: 3483: 3484: 3485: 3486: 3487: 3488: 3489: 3490: 3491: 3492: 3493: 3494: 3495: 3496: 3497: 3498: 3499: 3500: 3501: 3502: 3503: 3504: 3505: 3506: 3507: 3508: 3509: 3510: 3511: 3512: 3513: 3514: 3515: 3516: 3517: 3518: 3519: 3520: 3521: 3522: 3523: 3524: 3525: 3526: 3527: 3528: 3529: 3530: 3531: 3532: 3533: 3534: 3535: 3536: 3537: 3538: 3539: 3540: 3541: 3542: 3543: 3544: 3545: 3546: 3547: 3548: 3549: 3550: 3551: 3552: 3553: 3554: 3555: 3556: 3557: 3558: 3559: 3560: 3561: 3562: 3563: 3564: 3565: 3566: 3567: 3568: 3569: 3570: 3571: 3572: 3573: 3574: 3575: 3576: 3577: 3578: 3579: 3580: 3581: 3582: 3583: 3584: 3585: 3586: 3587: 3588: 3589: 3590: 3591: 3592: 3593: 3594: 3595: 3596: 3597: 3598: 3599: 3600: 3601: 3602: 3603: 3604: 3605: 3606: 3607: 3608: 3609: 3610: 3611: 3612: 3613: 3614: 3615: 3616: 3617: 3618: 3619: 3620: 3621: 3622: 3623: 3624: 3625: 3626: 3627: 3628: 3629: 3630: 3631: 3632: 3633: 3634: 3635: 3636: 3637: 3638: 3639: 3640: 3641: 3642: 3643: 3644: 3645: 3646: 3647: 3648: 3649: 3650: 3651: 3652: 3653: 3654: 3655: 3656: 3657: 3658: 3659: 3660: 3661: 3662: 3663: 3664: 3665: 3666: 3667: 3668: 3669: 3670: 3671: 3672: 3673: 3674: 3675: 3676: 3677: 3678: 3679: 3680: 3681: 3682: 3683: 3684: 3685: 3686: 3687: 3688: 3689: 3690: 3691: 3692: 3693: 3694: 3695: 3696: 3697: 3698: 3699: 3700: 3701: 3702: 3703: 3704: 3705: 3706: 3707: 3708: 3709: 3710: 3711: 3712: 3713: 3714: 3715: 3716: 3717: 3718: 3719: 3720: 3721: 3722: 3723: 3724: 3725: 3726: 3727: 3728: 3729: 3730: 3731: 3732: 3733: 3734: 3735: 3736: 3737: 3738: 3739: 3740: 3741: 3742: 3743: 3744: 3745: 3746: 3747: 3748: 3749: 3750: 3751: 3752: 3753: 3754: 3755: 3756: 3757: 3758: 3759: 3760: 3761: 3762: 3763: 3764: 3765: 3766: 3767: 3768: 3769: 3770: 3771: 3772: 3773: 3774: 3775: 3776: 3777: 3778: 3779: 3780: 3781: 3782: 3783: 3784: 3785: 3786: 3787: 3788: 3789: 3790: 3791: 3792: 3793: 3794: 3795: 3796: 3797: 3798: 3799: 3800: 3801: 3802: 3803: 3804: 3805: 3806: 3807: 3808: 3809: 3810: 3811: 3812: 3813: 3814: 3815: 3816: 3817: 3818: 3819: 3820: 3821: 3822: 3823: 3824: 3825: 3826: 3827: 3828: 3829: 3830: 3831: 3832: 3833: 3834: 3835: 3836: 3837: 3838: 3839: 3840: 3841: 3842: 3843: 3844: 3845: 3846: 3847: 3848: 3849: 3850: 3851: 3852: 3853: 3854: 3855: 3856: 3857: 3858: 3859: 3860: 3861: 3862: 3863: 3864: 3865: 3866: 3867: 3868: 3869: 3870: 3871: 3872: 3873: 3874: 3875: 3876: 3877: 3878: 3879: 3880: 3881: 3882: 3883: 3884: 3885: 3886: 3887: 3888: 3889: 3890: 3891: 3892: 3893: 3894: 3895: 3896: 3897: 3898: 3899: 3900: 
<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
///                                                            //
// module.tag.id3v2.php                                        //
// module for analyzing ID3v2 tags                             //
// dependencies: module.tag.id3v1.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
    exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);

class getid3_id3v2 extends getid3_handler
{
    public $StartingOffset = 0;

    /**
     * @return bool
     */
    public function Analyze() {
        $info = &$this->getid3->info;

        //    Overall tag structure:
        //        +-----------------------------+
        //        |      Header (10 bytes)      |
        //        +-----------------------------+
        //        |       Extended Header       |
        //        | (variable length, OPTIONAL) |
        //        +-----------------------------+
        //        |   Frames (variable length)  |
        //        +-----------------------------+
        //        |           Padding           |
        //        | (variable length, OPTIONAL) |
        //        +-----------------------------+
        //        | Footer (10 bytes, OPTIONAL) |
        //        +-----------------------------+

        //    Header
        //        ID3v2/file identifier      "ID3"
        //        ID3v2 version              $04 00
        //        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
        //        ID3v2 size             4 * %0xxxxxxx


        // shortcuts
        $info['id3v2']['header'] = true;
        $thisfile_id3v2                  = &$info['id3v2'];
        $thisfile_id3v2['flags']         =  array();
        $thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];


        $this->fseek($this->StartingOffset);
        $header = $this->fread(10);
        if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {

            $thisfile_id3v2['majorversion'] = ord($header[3]);
            $thisfile_id3v2['minorversion'] = ord($header[4]);

            // shortcut
            $id3v2_majorversion = &$thisfile_id3v2['majorversion'];

        } else {

            unset($info['id3v2']);
            return false;

        }

        if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)

            $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
            return false;

        }

        $id3_flags = ord($header[5]);
        switch ($id3v2_majorversion) {
            case 2:
                // %ab000000 in v2.2
                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
                $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
                break;

            case 3:
                // %abc00000 in v2.3
                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
                $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
                $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
                break;

            case 4:
                // %abcd0000 in v2.4
                $thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
                $thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
                $thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
                $thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
                break;
        }

        $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length

        $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
        $thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];



        // create 'encoding' key - used by getid3::HandleAllTags()
        // in ID3v2 every field can have it's own encoding type
        // so force everything to UTF-8 so it can be handled consistantly
        $thisfile_id3v2['encoding'] = 'UTF-8';


    //    Frames

    //        All ID3v2 frames consists of one frame header followed by one or more
    //        fields containing the actual information. The header is always 10
    //        bytes and laid out as follows:
    //
    //        Frame ID      $xx xx xx xx  (four characters)
    //        Size      4 * %0xxxxxxx
    //        Flags         $xx xx

        $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
        if (!empty($thisfile_id3v2['exthead']['length'])) {
            $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
        }
        if (!empty($thisfile_id3v2_flags['isfooter'])) {
            $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
        }
        if ($sizeofframes > 0) {

            $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable

            //    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
            if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
                $framedata = $this->DeUnsynchronise($framedata);
            }
            //        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
            //        of on tag level, making it easier to skip frames, increasing the streamability
            //        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
            //        there exists an unsynchronised frame, while the new unsynchronisation flag in
            //        the frame header [S:4.1.2] indicates unsynchronisation.


            //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
            $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header


            //    Extended Header
            if (!empty($thisfile_id3v2_flags['exthead'])) {
                $extended_header_offset = 0;

                if ($id3v2_majorversion == 3) {

                    // v2.3 definition:
                    //Extended header size  $xx xx xx xx   // 32-bit integer
                    //Extended Flags        $xx xx
                    //     %x0000000 %00000000 // v2.3
                    //     x - CRC data present
                    //Size of padding       $xx xx xx xx

                    $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
                    $extended_header_offset += 4;

                    $thisfile_id3v2['exthead']['flag_bytes'] = 2;
                    $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
                    $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];

                    $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);

                    $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
                    $extended_header_offset += 4;

                    if ($thisfile_id3v2['exthead']['flags']['crc']) {
                        $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
                        $extended_header_offset += 4;
                    }
                    $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];

                } elseif ($id3v2_majorversion == 4) {

                    // v2.4 definition:
                    //Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
                    //Number of flag bytes       $01
                    //Extended Flags             $xx
                    //     %0bcd0000 // v2.4
                    //     b - Tag is an update
                    //         Flag data length       $00
                    //     c - CRC data present
                    //         Flag data length       $05
                    //         Total frame CRC    5 * %0xxxxxxx
                    //     d - Tag restrictions
                    //         Flag data length       $01

                    $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
                    $extended_header_offset += 4;

                    $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
                    $extended_header_offset += 1;

                    $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
                    $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];

                    $thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
                    $thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
                    $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);

                    if ($thisfile_id3v2['exthead']['flags']['update']) {
                        $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
                        $extended_header_offset += 1;
                    }

                    if ($thisfile_id3v2['exthead']['flags']['crc']) {
                        $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
                        $extended_header_offset += 1;
                        $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
                        $extended_header_offset += $ext_header_chunk_length;
                    }

                    if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
                        $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
                        $extended_header_offset += 1;

                        // %ppqrrstt
                        $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
                        $extended_header_offset += 1;
                        $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
                        $thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
                        $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
                        $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
                        $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions

                        $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
                        $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
                        $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
                        $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
                        $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
                    }

                    if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
                        $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
                    }
                }

                $framedataoffset += $extended_header_offset;
                $framedata = substr($framedata, $extended_header_offset);
            } // end extended header


            while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
                if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
                    // insufficient room left in ID3v2 header for actual data - must be padding
                    $thisfile_id3v2['padding']['start']  = $framedataoffset;
                    $thisfile_id3v2['padding']['length'] = strlen($framedata);
                    $thisfile_id3v2['padding']['valid']  = true;
                    for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
                        if ($framedata[$i] != "\x00") {
                            $thisfile_id3v2['padding']['valid'] = false;
                            $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
                            $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
                            break;
                        }
                    }
                    break; // skip rest of ID3v2 header
                }
                $frame_header = null;
                $frame_name   = null;
                $frame_size   = null;
                $frame_flags  = null;
                if ($id3v2_majorversion == 2) {
                    // Frame ID  $xx xx xx (three characters)
                    // Size      $xx xx xx (24-bit integer)
                    // Flags     $xx xx

                    $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
                    $framedata    = substr($framedata, 6);    // and leave the rest in $framedata
                    $frame_name   = substr($frame_header, 0, 3);
                    $frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
                    $frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs

                } elseif ($id3v2_majorversion > 2) {

                    // Frame ID  $xx xx xx xx (four characters)
                    // Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
                    // Flags     $xx xx

                    $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
                    $framedata    = substr($framedata, 10);    // and leave the rest in $framedata

                    $frame_name = substr($frame_header, 0, 4);
                    if ($id3v2_majorversion == 3) {
                        $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
                    } else { // ID3v2.4+
                        $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
                    }

                    if ($frame_size < (strlen($framedata) + 4)) {
                        $nextFrameID = substr($framedata, $frame_size, 4);
                        if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
                            // next frame is OK
                        } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
                            // MP3ext known broken frames - "ok" for the purposes of this test
                        } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
                            $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
                            $id3v2_majorversion = 3;
                            $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
                        }
                    }


                    $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
                }

                if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
                    // padding encountered

                    $thisfile_id3v2['padding']['start']  = $framedataoffset;
                    $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
                    $thisfile_id3v2['padding']['valid']  = true;

                    $len = strlen($framedata);
                    for ($i = 0; $i < $len; $i++) {
                        if ($framedata[$i] != "\x00") {
                            $thisfile_id3v2['padding']['valid'] = false;
                            $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
                            $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
                            break;
                        }
                    }
                    break; // skip rest of ID3v2 header
                }

                if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
                    $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
                    $frame_name = $iTunesBrokenFrameNameFixed;
                }
                if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {

                    unset($parsedFrame);
                    $parsedFrame['frame_name']      = $frame_name;
                    $parsedFrame['frame_flags_raw'] = $frame_flags;
                    $parsedFrame['data']            = substr($framedata, 0, $frame_size);
                    $parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
                    $parsedFrame['dataoffset']      = $framedataoffset;

                    $this->ParseID3v2Frame($parsedFrame);
                    $thisfile_id3v2[$frame_name][] = $parsedFrame;

                    $framedata = substr($framedata, $frame_size);

                } else { // invalid frame length or FrameID

                    if ($frame_size <= strlen($framedata)) {

                        if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {

                            // next frame is valid, just skip the current frame
                            $framedata = substr($framedata, $frame_size);
                            $this->warning('Next ID3v2 frame is valid, skipping current frame.');

                        } else {

                            // next frame is invalid too, abort processing
                            //unset($framedata);
                            $framedata = null;
                            $this->error('Next ID3v2 frame is also invalid, aborting processing.');

                        }

                    } elseif ($frame_size == strlen($framedata)) {

                        // this is the last frame, just skip
                        $this->warning('This was the last ID3v2 frame.');

                    } else {

                        // next frame is invalid too, abort processing
                        //unset($framedata);
                        $framedata = null;
                        $this->warning('Invalid ID3v2 frame size, aborting.');

                    }
                    if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {

                        switch ($frame_name) {
                            case "\x00\x00".'MP':
                            case "\x00".'MP3':
                            case ' MP3':
                            case 'MP3e':
                            case "\x00".'MP':
                            case ' MP':
                            case 'MP3':
                                $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
                                break;

                            default:
                                $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
                                break;
                        }

                    } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {

                        $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');

                    } else {

                        $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');

                    }

                }
                $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));

            }

        }


    //    Footer

    //    The footer is a copy of the header, but with a different identifier.
    //        ID3v2 identifier           "3DI"
    //        ID3v2 version              $04 00
    //        ID3v2 flags                %abcd0000
    //        ID3v2 size             4 * %0xxxxxxx

        if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
            $footer = $this->fread(10);
            if (substr($footer, 0, 3) == '3DI') {
                $thisfile_id3v2['footer'] = true;
                $thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
                $thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
            }
            if ($thisfile_id3v2['majorversion_footer'] <= 4) {
                $id3_flags = ord($footer[5]);
                $thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
                $thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
                $thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
                $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);

                $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
            }
        } // end footer

        if (isset($thisfile_id3v2['comments']['genre'])) {
            $genres = array();
            foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
                foreach ($this->ParseID3v2GenreString($value) as $genre) {
                    $genres[] = $genre;
                }
            }
            $thisfile_id3v2['comments']['genre'] = array_unique($genres);
            unset($key, $value, $genres, $genre);
        }

        if (isset($thisfile_id3v2['comments']['track_number'])) {
            foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
                if (strstr($value, '/')) {
                    list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
                }
            }
        }

        if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
            $thisfile_id3v2['comments']['year'] = array($matches[1]);
        }


        if (!empty($thisfile_id3v2['TXXX'])) {
            // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
            foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
                switch ($txxx_array['description']) {
                    case 'replaygain_track_gain':
                        if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
                            $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
                        }
                        break;
                    case 'replaygain_track_peak':
                        if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
                            $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
                        }
                        break;
                    case 'replaygain_album_gain':
                        if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
                            $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
                        }
                        break;
                }
            }
        }


        // Set avdataoffset
        $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
        if (isset($thisfile_id3v2['footer'])) {
            $info['avdataoffset'] += 10;
        }

        return true;
    }

    /**
     * @param string $genrestring
     *
     * @return array
     */
    public function ParseID3v2GenreString($genrestring) {
        // Parse genres into arrays of genreName and genreID
        // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
        // ID3v2.4.x: '21' $00 'Eurodisco' $00
        $clean_genres = array();

        // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
        if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
            // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
            // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
            if (strpos($genrestring, '/') !== false) {
                $LegitimateSlashedGenreList = array(  // https://github.com/JamesHeinrich/getID3/issues/223
                    'Pop/Funk',    // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
                    'Cut-up/DJ',   // Discogs - https://www.discogs.com/style/cut-up/dj
                    'RnB/Swing',   // Discogs - https://www.discogs.com/style/rnb/swing
                    'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
                );
                $genrestring = str_replace('/', "\x00", $genrestring);
                foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
                    $genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
                }
            }

            // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
            if (strpos($genrestring, ';') !== false) {
                $genrestring = str_replace(';', "\x00", $genrestring);
            }
        }


        if (strpos($genrestring, "\x00") === false) {
            $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
        }

        $genre_elements = explode("\x00", $genrestring);
        foreach ($genre_elements as $element) {
            $element = trim($element);
            if ($element) {
                if (preg_match('#^[0-9]{1,3}$#', $element)) {
                    $clean_genres[] = getid3_id3v1::LookupGenreName($element);
                } else {
                    $clean_genres[] = str_replace('((', '(', $element);
                }
            }
        }
        return $clean_genres;
    }

    /**
     * @param array $parsedFrame
     *
     * @return bool
     */
    public function ParseID3v2Frame(&$parsedFrame) {

        // shortcuts
        $info = &$this->getid3->info;
        $id3v2_majorversion = $info['id3v2']['majorversion'];

        $parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
        if (empty($parsedFrame['framenamelong'])) {
            unset($parsedFrame['framenamelong']);
        }
        $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
        if (empty($parsedFrame['framenameshort'])) {
            unset($parsedFrame['framenameshort']);
        }

        if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
            if ($id3v2_majorversion == 3) {
                //    Frame Header Flags
                //    %abc00000 %ijk00000
                $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
                $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
                $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
                $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
                $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
                $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity

            } elseif ($id3v2_majorversion == 4) {
                //    Frame Header Flags
                //    %0abc0000 %0h00kmnp
                $parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
                $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
                $parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
                $parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
                $parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
                $parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
                $parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
                $parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator

                // Frame-level de-unsynchronisation - ID3v2.4
                if ($parsedFrame['flags']['Unsynchronisation']) {
                    $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
                }

                if ($parsedFrame['flags']['DataLengthIndicator']) {
                    $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
                    $parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
                }
            }

            //    Frame-level de-compression
            if ($parsedFrame['flags']['compression']) {
                $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
                if (!function_exists('gzuncompress')) {
                    $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
                } else {
                    if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
                    //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
                        $parsedFrame['data'] = $decompresseddata;
                        unset($decompresseddata);
                    } else {
                        $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
                    }
                }
            }
        }

        if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
            if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
                $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
            }
        }

        if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {

            $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
            switch ($parsedFrame['frame_name']) {
                case 'WCOM':
                    $warning .= ' (this is known to happen with files tagged by RioPort)';
                    break;

                default:
                    break;
            }
            $this->warning($warning);

        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
            (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
            //   There may be more than one 'UFID' frame in a tag,
            //   but only one with the same 'Owner identifier'.
            // <Header for 'Unique file identifier', ID: 'UFID'>
            // Owner identifier        <text string> $00
            // Identifier              <up to 64 bytes binary data>
            $exploded = explode("\x00", $parsedFrame['data'], 2);
            $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
            $parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');

        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
            //   There may be more than one 'TXXX' frame in each tag,
            //   but only one with the same description.
            // <Header for 'User defined text information frame', ID: 'TXXX'>
            // Text encoding     $xx
            // Description       <text string according to encoding> $00 (00)
            // Value             <text string according to encoding>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }
            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
            $parsedFrame['encodingid']  = $frame_textencoding;
            $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
            $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
            $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
                if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
                    $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
                } else {
                    $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
                }
            }
            //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain


        } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
            //   There may only be one text information frame of its kind in an tag.
            // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
            // excluding 'TXXX' described in 4.2.6.>
            // Text encoding                $xx
            // Information                  <text string(s) according to encoding>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
            }

            $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
            $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));

            $parsedFrame['encodingid'] = $frame_textencoding;
            $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
                // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
                // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
                // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
                switch ($parsedFrame['encoding']) {
                    case 'UTF-16':
                    case 'UTF-16BE':
                    case 'UTF-16LE':
                        $wordsize = 2;
                        break;
                    case 'ISO-8859-1':
                    case 'UTF-8':
                    default:
                        $wordsize = 1;
                        break;
                }
                $Txxx_elements = array();
                $Txxx_elements_start_offset = 0;
                for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
                    if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
                        $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
                        $Txxx_elements_start_offset = $i + $wordsize;
                    }
                }
                $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
                foreach ($Txxx_elements as $Txxx_element) {
                    $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
                    if (!empty($string)) {
                        $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
                    }
                }
                unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
            }

        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
            //   There may be more than one 'WXXX' frame in each tag,
            //   but only one with the same description
            // <Header for 'User defined URL link frame', ID: 'WXXX'>
            // Text encoding     $xx
            // Description       <text string according to encoding> $00 (00)
            // URL               <text string>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }
            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $parsedFrame['encodingid']  = $frame_textencoding;
            $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
            $parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
            $parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);

            if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
            }
            unset($parsedFrame['data']);


        } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
            //   There may only be one URL link frame of its kind in a tag,
            //   except when stated otherwise in the frame description
            // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
            // described in 4.3.2.>
            // URL              <text string>

            $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
            if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
            // http://id3.org/id3v2.3.0#sec4.4
            //   There may only be one 'IPL' frame in each tag
            // <Header for 'User defined URL link frame', ID: 'IPL'>
            // Text encoding     $xx
            // People list strings    <textstrings>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
            }
            $parsedFrame['encodingid'] = $frame_textencoding;
            $parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
            $parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);

            // https://www.getid3.org/phpBB3/viewtopic.php?t=1369
            // "this tag typically contains null terminated strings, which are associated in pairs"
            // "there are users that use the tag incorrectly"
            $IPLS_parts = array();
            if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
                $IPLS_parts_unsorted = array();
                if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
                    // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
                    $thisILPS  = '';
                    for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
                        $twobytes = substr($parsedFrame['data_raw'], $i, 2);
                        if ($twobytes === "\x00\x00") {
                            $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
                            $thisILPS  = '';
                        } else {
                            $thisILPS .= $twobytes;
                        }
                    }
                    if (strlen($thisILPS) > 2) { // 2-byte BOM
                        $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
                    }
                } else {
                    // ISO-8859-1 or UTF-8 or other single-byte-null character set
                    $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
                }
                if (count($IPLS_parts_unsorted) == 1) {
                    // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
                    foreach ($IPLS_parts_unsorted as $key => $value) {
                        $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
                        $position = '';
                        foreach ($IPLS_parts_sorted as $person) {
                            $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
                        }
                    }
                } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
                    $position = '';
                    $person   = '';
                    foreach ($IPLS_parts_unsorted as $key => $value) {
                        if (($key % 2) == 0) {
                            $position = $value;
                        } else {
                            $person   = $value;
                            $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
                            $position = '';
                            $person   = '';
                        }
                    }
                } else {
                    foreach ($IPLS_parts_unsorted as $key => $value) {
                        $IPLS_parts[] = array($value);
                    }
                }

            } else {
                $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
            }
            $parsedFrame['data'] = $IPLS_parts;

            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
            }


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
            //   There may only be one 'MCDI' frame in each tag
            // <Header for 'Music CD identifier', ID: 'MCDI'>
            // CD TOC                <binary data>

            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
            }


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
            //   There may only be one 'ETCO' frame in each tag
            // <Header for 'Event timing codes', ID: 'ETCO'>
            // Time stamp format    $xx
            //   Where time stamp format is:
            // $01  (32-bit value) MPEG frames from beginning of file
            // $02  (32-bit value) milliseconds from beginning of file
            //   Followed by a list of key events in the following format:
            // Type of event   $xx
            // Time stamp      $xx (xx ...)
            //   The 'Time stamp' is set to zero if directly at the beginning of the sound
            //   or after the previous event. All events MUST be sorted in chronological order.

            $frame_offset = 0;
            $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));

            while ($frame_offset < strlen($parsedFrame['data'])) {
                $parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
                $parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
                $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
                $frame_offset += 4;
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
            //   There may only be one 'MLLT' frame in each tag
            // <Header for 'Location lookup table', ID: 'MLLT'>
            // MPEG frames between reference  $xx xx
            // Bytes between reference        $xx xx xx
            // Milliseconds between reference $xx xx xx
            // Bits for bytes deviation       $xx
            // Bits for milliseconds dev.     $xx
            //   Then for every reference the following data is included;
            // Deviation in bytes         %xxx....
            // Deviation in milliseconds  %xxx....

            $frame_offset = 0;
            $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
            $parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
            $parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
            $parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
            $parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
            $parsedFrame['data'] = substr($parsedFrame['data'], 10);
            $deviationbitstream = '';
            while ($frame_offset < strlen($parsedFrame['data'])) {
                $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
            }
            $reference_counter = 0;
            while (strlen($deviationbitstream) > 0) {
                $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
                $parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
                $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
                $reference_counter++;
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
            //   There may only be one 'SYTC' frame in each tag
            // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
            // Time stamp format   $xx
            // Tempo data          <binary data>
            //   Where time stamp format is:
            // $01  (32-bit value) MPEG frames from beginning of file
            // $02  (32-bit value) milliseconds from beginning of file

            $frame_offset = 0;
            $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $timestamp_counter = 0;
            while ($frame_offset < strlen($parsedFrame['data'])) {
                $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
                if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
                    $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
                }
                $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
                $frame_offset += 4;
                $timestamp_counter++;
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {     // 4.9   ULT  Unsynchronised lyric/text transcription
            //   There may be more than one 'Unsynchronised lyrics/text transcription' frame
            //   in each tag, but only one with the same language and content descriptor.
            // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
            // Text encoding        $xx
            // Language             $xx xx xx
            // Content descriptor   <text string according to encoding> $00 (00)
            // Lyrics/text          <full text string according to encoding>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }
            $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
            $frame_offset += 3;
            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
            $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
            $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);

            $parsedFrame['encodingid']   = $frame_textencoding;
            $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['language']     = $frame_language;
            $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
            //   There may be more than one 'SYLT' frame in each tag,
            //   but only one with the same language and content descriptor.
            // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
            // Text encoding        $xx
            // Language             $xx xx xx
            // Time stamp format    $xx
            //   $01  (32-bit value) MPEG frames from beginning of file
            //   $02  (32-bit value) milliseconds from beginning of file
            // Content type         $xx
            // Content descriptor   <text string according to encoding> $00 (00)
            //   Terminated text to be synced (typically a syllable)
            //   Sync identifier (terminator to above string)   $00 (00)
            //   Time stamp                                     $xx (xx ...)

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }
            $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
            $frame_offset += 3;
            $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
            $parsedFrame['encodingid']      = $frame_textencoding;
            $parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['language']        = $frame_language;
            $parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);

            $timestampindex = 0;
            $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
            while (strlen($frame_remainingdata)) {
                $frame_offset = 0;
                $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
                if ($frame_terminatorpos === false) {
                    $frame_remainingdata = '';
                } else {
                    if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                        $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
                    }
                    $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);

                    $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
                    if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
                        // timestamp probably omitted for first data item
                    } else {
                        $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
                        $frame_remainingdata = substr($frame_remainingdata, 4);
                    }
                    $timestampindex++;
                }
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
            //   There may be more than one comment frame in each tag,
            //   but only one with the same language and content descriptor.
            // <Header for 'Comment', ID: 'COMM'>
            // Text encoding          $xx
            // Language               $xx xx xx
            // Short content descrip. <text string according to encoding> $00 (00)
            // The actual text        <full text string according to encoding>

            if (strlen($parsedFrame['data']) < 5) {

                $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);

            } else {

                $frame_offset = 0;
                $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
                $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
                if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                    $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                    $frame_textencoding_terminator = "\x00";
                }
                $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
                $frame_offset += 3;
                $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
                if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                    $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
                }
                $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
                $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
                $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
                $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);

                $parsedFrame['encodingid']   = $frame_textencoding;
                $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

                $parsedFrame['language']     = $frame_language;
                $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
                $parsedFrame['data']         = $frame_text;
                if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                    $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
                    if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
                        $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
                    } else {
                        $info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
                    }
                }

            }

        } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
            //   There may be more than one 'RVA2' frame in each tag,
            //   but only one with the same identification string
            // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
            // Identification          <text string> $00
            //   The 'identification' string is used to identify the situation and/or
            //   device where this adjustment should apply. The following is then
            //   repeated for every channel:
            // Type of channel         $xx
            // Volume adjustment       $xx xx
            // Bits representing peak  $xx
            // Peak volume             $xx (xx ...)

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
            $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
            if (ord($frame_idstring) === 0) {
                $frame_idstring = '';
            }
            $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
            $parsedFrame['description'] = $frame_idstring;
            $RVA2channelcounter = 0;
            while (strlen($frame_remainingdata) >= 5) {
                $frame_offset = 0;
                $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
                $parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
                $parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
                $parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
                $frame_offset += 2;
                $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
                if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
                    $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
                    break;
                }
                $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
                $parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
                $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
                $RVA2channelcounter++;
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
                  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
            //   There may only be one 'RVA' frame in each tag
            // <Header for 'Relative volume adjustment', ID: 'RVA'>
            // ID3v2.2 => Increment/decrement     %000000ba
            // ID3v2.3 => Increment/decrement     %00fedcba
            // Bits used for volume descr.        $xx
            // Relative volume change, right      $xx xx (xx ...) // a
            // Relative volume change, left       $xx xx (xx ...) // b
            // Peak volume right                  $xx xx (xx ...)
            // Peak volume left                   $xx xx (xx ...)
            //   ID3v2.3 only, optional (not present in ID3v2.2):
            // Relative volume change, right back $xx xx (xx ...) // c
            // Relative volume change, left back  $xx xx (xx ...) // d
            // Peak volume right back             $xx xx (xx ...)
            // Peak volume left back              $xx xx (xx ...)
            //   ID3v2.3 only, optional (not present in ID3v2.2):
            // Relative volume change, center     $xx xx (xx ...) // e
            // Peak volume center                 $xx xx (xx ...)
            //   ID3v2.3 only, optional (not present in ID3v2.2):
            // Relative volume change, bass       $xx xx (xx ...) // f
            // Peak volume bass                   $xx xx (xx ...)

            $frame_offset = 0;
            $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
            $parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
            $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
            $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
            if ($parsedFrame['incdec']['right'] === false) {
                $parsedFrame['volumechange']['right'] *= -1;
            }
            $frame_offset += $frame_bytesvolume;
            $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
            if ($parsedFrame['incdec']['left'] === false) {
                $parsedFrame['volumechange']['left'] *= -1;
            }
            $frame_offset += $frame_bytesvolume;
            $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
            $frame_offset += $frame_bytesvolume;
            $parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
            $frame_offset += $frame_bytesvolume;
            if ($id3v2_majorversion == 3) {
                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
                if (strlen($parsedFrame['data']) > 0) {
                    $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
                    $parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
                    $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    if ($parsedFrame['incdec']['rightrear'] === false) {
                        $parsedFrame['volumechange']['rightrear'] *= -1;
                    }
                    $frame_offset += $frame_bytesvolume;
                    $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    if ($parsedFrame['incdec']['leftrear'] === false) {
                        $parsedFrame['volumechange']['leftrear'] *= -1;
                    }
                    $frame_offset += $frame_bytesvolume;
                    $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    $frame_offset += $frame_bytesvolume;
                    $parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    $frame_offset += $frame_bytesvolume;
                }
                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
                if (strlen($parsedFrame['data']) > 0) {
                    $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
                    $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    if ($parsedFrame['incdec']['center'] === false) {
                        $parsedFrame['volumechange']['center'] *= -1;
                    }
                    $frame_offset += $frame_bytesvolume;
                    $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    $frame_offset += $frame_bytesvolume;
                }
                $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
                if (strlen($parsedFrame['data']) > 0) {
                    $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
                    $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    if ($parsedFrame['incdec']['bass'] === false) {
                        $parsedFrame['volumechange']['bass'] *= -1;
                    }
                    $frame_offset += $frame_bytesvolume;
                    $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
                    $frame_offset += $frame_bytesvolume;
                }
            }
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
            //   There may be more than one 'EQU2' frame in each tag,
            //   but only one with the same identification string
            // <Header of 'Equalisation (2)', ID: 'EQU2'>
            // Interpolation method  $xx
            //   $00  Band
            //   $01  Linear
            // Identification        <text string> $00
            //   The following is then repeated for every adjustment point
            // Frequency          $xx xx
            // Volume adjustment  $xx xx

            $frame_offset = 0;
            $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_idstring) === 0) {
                $frame_idstring = '';
            }
            $parsedFrame['description'] = $frame_idstring;
            $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
            while (strlen($frame_remainingdata)) {
                $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
                $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
                $frame_remainingdata = substr($frame_remainingdata, 4);
            }
            $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
            //   There may only be one 'EQUA' frame in each tag
            // <Header for 'Relative volume adjustment', ID: 'EQU'>
            // Adjustment bits    $xx
            //   This is followed by 2 bytes + ('adjustment bits' rounded up to the
            //   nearest byte) for every equalisation band in the following format,
            //   giving a frequency range of 0 - 32767Hz:
            // Increment/decrement   %x (MSB of the Frequency)
            // Frequency             (lower 15 bits)
            // Adjustment            $xx (xx ...)

            $frame_offset = 0;
            $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
            $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);

            $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
            while (strlen($frame_remainingdata) > 0) {
                $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
                $frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
                $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
                $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
                $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
                if ($parsedFrame[$frame_frequency]['incdec'] === false) {
                    $parsedFrame[$frame_frequency]['adjustment'] *= -1;
                }
                $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
            }
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
            //   There may only be one 'RVRB' frame in each tag.
            // <Header for 'Reverb', ID: 'RVRB'>
            // Reverb left (ms)                 $xx xx
            // Reverb right (ms)                $xx xx
            // Reverb bounces, left             $xx
            // Reverb bounces, right            $xx
            // Reverb feedback, left to left    $xx
            // Reverb feedback, left to right   $xx
            // Reverb feedback, right to right  $xx
            // Reverb feedback, right to left   $xx
            // Premix left to right             $xx
            // Premix right to left             $xx

            $frame_offset = 0;
            $parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
            //   There may be several pictures attached to one file,
            //   each in their individual 'APIC' frame, but only one
            //   with the same content descriptor
            // <Header for 'Attached picture', ID: 'APIC'>
            // Text encoding      $xx
            // ID3v2.3+ => MIME type          <text string> $00
            // ID3v2.2  => Image format       $xx xx xx
            // Picture type       $xx
            // Description        <text string according to encoding> $00 (00)
            // Picture data       <binary data>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }

            if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
                $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
                if (strtolower($frame_imagetype) == 'ima') {
                    // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
                    // MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
                    $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
                    $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
                    if (ord($frame_mimetype) === 0) {
                        $frame_mimetype = '';
                    }
                    $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
                    if ($frame_imagetype == 'JPEG') {
                        $frame_imagetype = 'JPG';
                    }
                    $frame_offset = $frame_terminatorpos + strlen("\x00");
                } else {
                    $frame_offset += 3;
                }
            }
            if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
                $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
                $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
                if (ord($frame_mimetype) === 0) {
                    $frame_mimetype = '';
                }
                $frame_offset = $frame_terminatorpos + strlen("\x00");
            }

            $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));

            if ($frame_offset >= $parsedFrame['datalength']) {
                $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
            } else {
                $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
                if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                    $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
                }
                $parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
                $parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
                $parsedFrame['encodingid']    = $frame_textencoding;
                $parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);

                if ($id3v2_majorversion == 2) {
                    $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
                } else {
                    $parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
                }
                $parsedFrame['picturetypeid'] = $frame_picturetype;
                $parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
                $parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
                $parsedFrame['datalength']    = strlen($parsedFrame['data']);

                $parsedFrame['image_mime']    = '';
                $imageinfo = array();
                if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
                    if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
                        $parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
                        if ($imagechunkcheck[0]) {
                            $parsedFrame['image_width']  = $imagechunkcheck[0];
                        }
                        if ($imagechunkcheck[1]) {
                            $parsedFrame['image_height'] = $imagechunkcheck[1];
                        }
                    }
                }

                do {
                    if ($this->getid3->option_save_attachments === false) {
                        // skip entirely
                        unset($parsedFrame['data']);
                        break;
                    }
                    $dir = '';
                    if ($this->getid3->option_save_attachments === true) {
                        // great
/*
                    } elseif (is_int($this->getid3->option_save_attachments)) {
                        if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
                            // too big, skip
                            $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
                            unset($parsedFrame['data']);
                            break;
                        }
*/
                    } elseif (is_string($this->getid3->option_save_attachments)) {
                        $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
                        if (!is_dir($dir) || !getID3::is_writable($dir)) {
                            // cannot write, skip
                            $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
                            unset($parsedFrame['data']);
                            break;
                        }
                    }
                    // if we get this far, must be OK
                    if (is_string($this->getid3->option_save_attachments)) {
                        $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
                        if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
                            file_put_contents($destination_filename, $parsedFrame['data']);
                        } else {
                            $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
                        }
                        $parsedFrame['data_filename'] = $destination_filename;
                        unset($parsedFrame['data']);
                    } else {
                        if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                            if (!isset($info['id3v2']['comments']['picture'])) {
                                $info['id3v2']['comments']['picture'] = array();
                            }
                            $comments_picture_data = array();
                            foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
                                if (isset($parsedFrame[$picture_key])) {
                                    $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
                                }
                            }
                            $info['id3v2']['comments']['picture'][] = $comments_picture_data;
                            unset($comments_picture_data);
                        }
                    }
                } while (false);
            }

        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
            //   There may be more than one 'GEOB' frame in each tag,
            //   but only one with the same content descriptor
            // <Header for 'General encapsulated object', ID: 'GEOB'>
            // Text encoding          $xx
            // MIME type              <text string> $00
            // Filename               <text string according to encoding> $00 (00)
            // Content description    <text string according to encoding> $00 (00)
            // Encapsulated object    <binary data>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_mimetype) === 0) {
                $frame_mimetype = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_filename) === 0) {
                $frame_filename = '';
            }
            $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
            $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

            $parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
            $parsedFrame['encodingid']  = $frame_textencoding;
            $parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['mime']        = $frame_mimetype;
            $parsedFrame['filename']    = $frame_filename;
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
            //   There may only be one 'PCNT' frame in each tag.
            //   When the counter reaches all one's, one byte is inserted in
            //   front of the counter thus making the counter eight bits bigger
            // <Header for 'Play counter', ID: 'PCNT'>
            // Counter        $xx xx xx xx (xx ...)

            $parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
            //   There may be more than one 'POPM' frame in each tag,
            //   but only one with the same email address
            // <Header for 'Popularimeter', ID: 'POPM'>
            // Email to user   <text string> $00
            // Rating          $xx
            // Counter         $xx xx xx xx (xx ...)

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_emailaddress) === 0) {
                $frame_emailaddress = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");
            $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
            $parsedFrame['email']   = $frame_emailaddress;
            $parsedFrame['rating']  = $frame_rating;
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
            //   There may only be one 'RBUF' frame in each tag
            // <Header for 'Recommended buffer size', ID: 'RBUF'>
            // Buffer size               $xx xx xx
            // Embedded info flag        %0000000x
            // Offset to next tag        $xx xx xx xx

            $frame_offset = 0;
            $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
            $frame_offset += 3;

            $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
            $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
            //   There may be more than one 'CRM' frame in a tag,
            //   but only one with the same 'owner identifier'
            // <Header for 'Encrypted meta frame', ID: 'CRM'>
            // Owner identifier      <textstring> $00 (00)
            // Content/explanation   <textstring> $00 (00)
            // Encrypted datablock   <binary data>

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $parsedFrame['ownerid']     = $frame_ownerid;
            $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
            //   There may be more than one 'AENC' frames in a tag,
            //   but only one with the same 'Owner identifier'
            // <Header for 'Audio encryption', ID: 'AENC'>
            // Owner identifier   <text string> $00
            // Preview start      $xx xx
            // Preview length     $xx xx
            // Encryption info    <binary data>

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_ownerid) === 0) {
                $frame_ownerid = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");
            $parsedFrame['ownerid'] = $frame_ownerid;
            $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
            unset($parsedFrame['data']);


        } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
                (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
            //   There may be more than one 'LINK' frame in a tag,
            //   but only one with the same contents
            // <Header for 'Linked information', ID: 'LINK'>
            // ID3v2.3+ => Frame identifier   $xx xx xx xx
            // ID3v2.2  => Frame identifier   $xx xx xx
            // URL                            <text string> $00
            // ID and additional data         <text string(s)>

            $frame_offset = 0;
            if ($id3v2_majorversion == 2) {
                $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
                $frame_offset += 3;
            } else {
                $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
                $frame_offset += 4;
            }

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_url) === 0) {
                $frame_url = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");
            $parsedFrame['url'] = $frame_url;

            $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
            if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
            }
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
            //   There may only be one 'POSS' frame in each tag
            // <Head for 'Position synchronisation', ID: 'POSS'>
            // Time stamp format         $xx
            // Position                  $xx (xx ...)

            $frame_offset = 0;
            $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
            //   There may be more than one 'Terms of use' frame in a tag,
            //   but only one with the same 'Language'
            // <Header for 'Terms of use frame', ID: 'USER'>
            // Text encoding        $xx
            // Language             $xx xx xx
            // The actual text      <text string according to encoding>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
            }
            $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
            $frame_offset += 3;
            $parsedFrame['language']     = $frame_language;
            $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
            $parsedFrame['encodingid']   = $frame_textencoding;
            $parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
            $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
            if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
                $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
            }
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
            //   There may only be one 'OWNE' frame in a tag
            // <Header for 'Ownership frame', ID: 'OWNE'>
            // Text encoding     $xx
            // Price paid        <text string> $00
            // Date of purch.    <text string>
            // Seller            <text string according to encoding>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
            }
            $parsedFrame['encodingid'] = $frame_textencoding;
            $parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
            $parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
            $parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);

            $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
            if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
                $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
            }
            $frame_offset += 8;

            $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
            $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
            //   There may be more than one 'commercial frame' in a tag,
            //   but no two may be identical
            // <Header for 'Commercial frame', ID: 'COMR'>
            // Text encoding      $xx
            // Price string       <text string> $00
            // Valid until        <text string>
            // Contact URL        <text string> $00
            // Received as        $xx
            // Name of seller     <text string according to encoding> $00 (00)
            // Description        <text string according to encoding> $00 (00)
            // Picture MIME type  <string> $00
            // Seller logo        <binary data>

            $frame_offset = 0;
            $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
            if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
                $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
                $frame_textencoding_terminator = "\x00";
            }

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $frame_offset = $frame_terminatorpos + strlen("\x00");
            $frame_rawpricearray = explode('/', $frame_pricestring);
            foreach ($frame_rawpricearray as $key => $val) {
                $frame_currencyid = substr($val, 0, 3);
                $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
                $parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
            }

            $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
            $frame_offset += 8;

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));

            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_sellername) === 0) {
                $frame_sellername = '';
            }
            $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

            $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
            if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
                $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
            }
            $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
            $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);

            $parsedFrame['encodingid']        = $frame_textencoding;
            $parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);

            $parsedFrame['pricevaliduntil']   = $frame_datestring;
            $parsedFrame['contacturl']        = $frame_contacturl;
            $parsedFrame['receivedasid']      = $frame_receivedasid;
            $parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
            $parsedFrame['sellername']        = $frame_sellername;
            $parsedFrame['mime']              = $frame_mimetype;
            $parsedFrame['logo']              = $frame_sellerlogo;
            unset($parsedFrame['data']);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
            //   There may be several 'ENCR' frames in a tag,
            //   but only one containing the same symbol
            //   and only one containing the same owner identifier
            // <Header for 'Encryption method registration', ID: 'ENCR'>
            // Owner identifier    <text string> $00
            // Method symbol       $xx
            // Encryption data     <binary data>

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_ownerid) === 0) {
                $frame_ownerid = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $parsedFrame['ownerid']      = $frame_ownerid;
            $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)

            //   There may be several 'GRID' frames in a tag,
            //   but only one containing the same symbol
            //   and only one containing the same owner identifier
            // <Header for 'Group ID registration', ID: 'GRID'>
            // Owner identifier      <text string> $00
            // Group symbol          $xx
            // Group dependent data  <binary data>

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_ownerid) === 0) {
                $frame_ownerid = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $parsedFrame['ownerid']       = $frame_ownerid;
            $parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
            //   The tag may contain more than one 'PRIV' frame
            //   but only with different contents
            // <Header for 'Private frame', ID: 'PRIV'>
            // Owner identifier      <text string> $00
            // The private data      <binary data>

            $frame_offset = 0;
            $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
            $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
            if (ord($frame_ownerid) === 0) {
                $frame_ownerid = '';
            }
            $frame_offset = $frame_terminatorpos + strlen("\x00");

            $parsedFrame['ownerid'] = $frame_ownerid;
            $parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);


        } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
            //   There may be more than one 'signature frame' in a tag,
            //   but no two may be identical
            // <Header for 'Signature frame', ID: 'SIGN'>
            // Group symbol      $xx
            // Signature         <binary data>

            $frame_offset = 0;
            $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);


        } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
            //   There may only be one 'seek frame' in a tag
            // <Header for 'Seek frame', ID: 'SEEK'>
            // Minimum offset to next tag       $xx xx xx xx

            $frame_offset = 0;
            $parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));


        } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
            //   There may only be one 'audio seek point index' frame in a tag
            // <Header for 'Seek Point Index', ID: 'ASPI'>
            // Indexed data start (S)         $xx xx xx xx
            // Indexed data length (L)        $xx xx xx xx
            // Number of index points (N)     $xx xx
            // Bits per index point (b)       $xx
            //   Then for every index point the following data is included:
            // Fraction at index (Fi)          $xx (xx)

            $frame_offset = 0;
            $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            $frame_offset += 4;
            $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            $frame_offset += 4;
            $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
            $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
            for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
                $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
                $frame_offset += $frame_bytesperpoint;
            }
            unset($parsedFrame['data']);

        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
            // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
            //   There may only be one 'RGAD' frame in a tag
            // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
            // Peak Amplitude                      $xx $xx $xx $xx
            // Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
            // Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
            //   a - name code
            //   b - originator code
            //   c - sign bit
            //   d - replay gain adjustment

            $frame_offset = 0;
            $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
            $frame_offset += 4;
            $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
            $frame_offset += 2;
            $parsedFrame['raw']['track']['name']       = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
            $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
            $parsedFrame['raw']['track']['signbit']    = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
            $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
            $parsedFrame['raw']['album']['name']       = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
            $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
            $parsedFrame['raw']['album']['signbit']    = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
            $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
            $parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
            $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
            $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
            $parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
            $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
            $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);

            $info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
            $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
            $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
            $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
            $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];

            unset($parsedFrame['data']);

        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
            // http://id3.org/id3v2-chapters-1.0
            // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
            // Element ID      <text string> $00
            // Start time      $xx xx xx xx
            // End time        $xx xx xx xx
            // Start offset    $xx xx xx xx
            // End offset      $xx xx xx xx
            // <Optional embedded sub-frames>

            $frame_offset = 0;
            @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
            $frame_offset += strlen($parsedFrame['element_id']."\x00");
            $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            $frame_offset += 4;
            $parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            $frame_offset += 4;
            if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
                // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
                $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            }
            $frame_offset += 4;
            if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
                // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
                $parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
            }
            $frame_offset += 4;

            if ($frame_offset < strlen($parsedFrame['data'])) {
                $parsedFrame['subframes'] = array();
                while ($frame_offset < strlen($parsedFrame['data'])) {
                    // <Optional embedded sub-frames>
                    $subframe = array();
                    $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
                    $frame_offset += 4;
                    $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
                    $frame_offset += 4;
                    $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
                    $frame_offset += 2;
                    if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
                        $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
                        break;
                    }
                    $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
                    $frame_offset += $subframe['size'];

                    $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
                    $subframe['text']       =     substr($subframe_rawdata, 1);
                    $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
                    $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
                    switch (substr($encoding_converted_text, 0, 2)) {
                        case "\xFF\xFE":
                        case "\xFE\xFF":
                            switch (strtoupper($info['id3v2']['encoding'])) {
                                case 'ISO-8859-1':
                                case 'UTF-8':
                                    $encoding_converted_text = substr($encoding_converted_text, 2);
                                    // remove unwanted byte-order-marks
                                    break;
                                default:
                                    // ignore
                                    break;
                            }
                            break;
                        default:
                            // do not remove BOM
                            break;
                    }

                    switch ($subframe['name']) {
                        case 'TIT2':
                            $parsedFrame['chapter_name']        = $encoding_converted_text;
                            $parsedFrame['subframes'][] = $subframe;
                            break;
                        case 'TIT3':
                            $parsedFrame['chapter_description'] = $encoding_converted_text;
                            $parsedFrame['subframes'][] = $subframe;
                            break;
                        case 'WXXX':
                            list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
                            $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
                            $parsedFrame['subframes'][] = $subframe;
                            break;
                        case 'APIC':
                            if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
                                list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
                                $subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
                                $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
                                $subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
                                if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
                                    // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
                                    // the above regex assumes one byte, if it's actually two then strip the second one here
                                    $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
                                }
                                $subframe['data'] = $subframe_apic_picturedata;
                                unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
                                unset($subframe['text'], $parsedFrame['text']);
                                $parsedFrame['subframes'][] = $subframe;
                                $parsedFrame['picture_present'] = true;
                            } else {
                                $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
                            }
                            break;
                        default:
                            $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
                            break;
                    }
                }
                unset($subframe_rawdata, $subframe, $encoding_converted_text);
                unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
            }

            $id3v2_chapter_entry = array();
            foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
                if (isset($parsedFrame[$id3v2_chapter_key])) {
                    $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
                }
            }
            if (!isset($info['id3v2']['chapters'])) {
                $info['id3v2']['chapters'] = array();
            }
            $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
            unset($id3v2_chapter_entry, $id3v2_chapter_key);


        } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
            // http://id3.org/id3v2-chapters-1.0
            // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
            // Element ID      <text string> $00
            // CTOC flags        %xx
            // Entry count       $xx
            // Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
            // <Optional embedded sub-frames>

            $frame_offset = 0;
            @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
            $frame_offset += strlen($parsedFrame['element_id']."\x00");
            $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
            $frame_offset += 1;
            $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
            $frame_offset += 1;

            $terminator_position = null;
            for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
                $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
                $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
                $frame_offset = $terminator_position + 1;
            }

            $parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
            $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);

            unset($ctoc_flags_raw, $terminator_position);

            if ($frame_offset < strlen($parsedFrame['data'])) {
                $parsedFrame['subframes'] = array();
                while ($frame_offset < strlen($parsedFrame['data'])) {
                    // <Optional embedded sub-frames>
                    $subframe = array();
                    $subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
                    $frame_offset += 4;
                    $subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
                    $frame_offset += 4;
                    $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
                    $frame_offset += 2;
                    if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
                        $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
                        break;
                    }
                    $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
                    $frame_offset += $subframe['size'];

                    $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
                    $subframe['text']       =     substr($subframe_rawdata, 1);
                    $subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
                    $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
                    switch (substr($encoding_converted_text, 0, 2)) {
                        case "\xFF\xFE":
                        case "\xFE\xFF":
                            switch (strtoupper($info['id3v2']['encoding'])) {
                                case 'ISO-8859-1':
                                case 'UTF-8':
                                    $encoding_converted_text = substr($encoding_converted_text, 2);
                                    // remove unwanted byte-order-marks
                                    break;
                                default:
                                    // ignore
                                    break;
                            }
                            break;
                        default:
                            // do not remove BOM
                            break;
                    }

                    if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
                        if ($subframe['name'] == 'TIT2') {
                            $parsedFrame['toc_name']        = $encoding_converted_text;
                        } elseif ($subframe['name'] == 'TIT3') {
                            $parsedFrame['toc_description'] = $encoding_converted_text;
                        }
                        $parsedFrame['subframes'][] = $subframe;
                    } else {
                        $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
                    }
                }
                unset($subframe_rawdata, $subframe, $encoding_converted_text);
            }

        }

        return true;
    }

    /**
     * @param string $data
     *
     * @return string
     */
    public function DeUnsynchronise($data) {
        return str_replace("\xFF\x00", "\xFF", $data);
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
        static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
            0x00 => 'No more than 128 frames and 1 MB total tag size',
            0x01 => 'No more than 64 frames and 128 KB total tag size',
            0x02 => 'No more than 32 frames and 40 KB total tag size',
            0x03 => 'No more than 32 frames and 4 KB total tag size',
        );
        return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
        static $LookupExtendedHeaderRestrictionsTextEncodings = array(
            0x00 => 'No restrictions',
            0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
        );
        return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
        static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
            0x00 => 'No restrictions',
            0x01 => 'No string is longer than 1024 characters',
            0x02 => 'No string is longer than 128 characters',
            0x03 => 'No string is longer than 30 characters',
        );
        return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
        static $LookupExtendedHeaderRestrictionsImageEncoding = array(
            0x00 => 'No restrictions',
            0x01 => 'Images are encoded only with PNG or JPEG',
        );
        return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
        static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
            0x00 => 'No restrictions',
            0x01 => 'All images are 256x256 pixels or smaller',
            0x02 => 'All images are 64x64 pixels or smaller',
            0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
        );
        return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
    }

    /**
     * @param string $currencyid
     *
     * @return string
     */
    public function LookupCurrencyUnits($currencyid) {

        $begin = __LINE__;

        /** This is not a comment!


            AED Dirhams
            AFA Afghanis
            ALL Leke
            AMD Drams
            ANG Guilders
            AOA Kwanza
            ARS Pesos
            ATS Schillings
            AUD Dollars
            AWG Guilders
            AZM Manats
            BAM Convertible Marka
            BBD Dollars
            BDT Taka
            BEF Francs
            BGL Leva
            BHD Dinars
            BIF Francs
            BMD Dollars
            BND Dollars
            BOB Bolivianos
            BRL Brazil Real
            BSD Dollars
            BTN Ngultrum
            BWP Pulas
            BYR Rubles
            BZD Dollars
            CAD Dollars
            CDF Congolese Francs
            CHF Francs
            CLP Pesos
            CNY Yuan Renminbi
            COP Pesos
            CRC Colones
            CUP Pesos
            CVE Escudos
            CYP Pounds
            CZK Koruny
            DEM Deutsche Marks
            DJF Francs
            DKK Kroner
            DOP Pesos
            DZD Algeria Dinars
            EEK Krooni
            EGP Pounds
            ERN Nakfa
            ESP Pesetas
            ETB Birr
            EUR Euro
            FIM Markkaa
            FJD Dollars
            FKP Pounds
            FRF Francs
            GBP Pounds
            GEL Lari
            GGP Pounds
            GHC Cedis
            GIP Pounds
            GMD Dalasi
            GNF Francs
            GRD Drachmae
            GTQ Quetzales
            GYD Dollars
            HKD Dollars
            HNL Lempiras
            HRK Kuna
            HTG Gourdes
            HUF Forints
            IDR Rupiahs
            IEP Pounds
            ILS New Shekels
            IMP Pounds
            INR Rupees
            IQD Dinars
            IRR Rials
            ISK Kronur
            ITL Lire
            JEP Pounds
            JMD Dollars
            JOD Dinars
            JPY Yen
            KES Shillings
            KGS Soms
            KHR Riels
            KMF Francs
            KPW Won
            KWD Dinars
            KYD Dollars
            KZT Tenge
            LAK Kips
            LBP Pounds
            LKR Rupees
            LRD Dollars
            LSL Maloti
            LTL Litai
            LUF Francs
            LVL Lati
            LYD Dinars
            MAD Dirhams
            MDL Lei
            MGF Malagasy Francs
            MKD Denars
            MMK Kyats
            MNT Tugriks
            MOP Patacas
            MRO Ouguiyas
            MTL Liri
            MUR Rupees
            MVR Rufiyaa
            MWK Kwachas
            MXN Pesos
            MYR Ringgits
            MZM Meticais
            NAD Dollars
            NGN Nairas
            NIO Gold Cordobas
            NLG Guilders
            NOK Krone
            NPR Nepal Rupees
            NZD Dollars
            OMR Rials
            PAB Balboa
            PEN Nuevos Soles
            PGK Kina
            PHP Pesos
            PKR Rupees
            PLN Zlotych
            PTE Escudos
            PYG Guarani
            QAR Rials
            ROL Lei
            RUR Rubles
            RWF Rwanda Francs
            SAR Riyals
            SBD Dollars
            SCR Rupees
            SDD Dinars
            SEK Kronor
            SGD Dollars
            SHP Pounds
            SIT Tolars
            SKK Koruny
            SLL Leones
            SOS Shillings
            SPL Luigini
            SRG Guilders
            STD Dobras
            SVC Colones
            SYP Pounds
            SZL Emalangeni
            THB Baht
            TJR Rubles
            TMM Manats
            TND Dinars
            TOP Pa'anga
            TRL Liras
            TTD Dollars
            TVD Tuvalu Dollars
            TWD New Dollars
            TZS Shillings
            UAH Hryvnia
            UGX Shillings
            USD Dollars
            UYU Pesos
            UZS Sums
            VAL Lire
            VEB Bolivares
            VND Dong
            VUV Vatu
            WST Tala
            XAF Francs
            XAG Ounces
            XAU Ounces
            XCD Dollars
            XDR Special Drawing Rights
            XPD Ounces
            XPF Francs
            XPT Ounces
            YER Rials
            YUM New Dinars
            ZAR Rand
            ZMK Kwacha
            ZWD Zimbabwe Dollars

        */

        return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
    }

    /**
     * @param string $currencyid
     *
     * @return string
     */
    public function LookupCurrencyCountry($currencyid) {

        $begin = __LINE__;

        /** This is not a comment!

            AED United Arab Emirates
            AFA Afghanistan
            ALL Albania
            AMD Armenia
            ANG Netherlands Antilles
            AOA Angola
            ARS Argentina
            ATS Austria
            AUD Australia
            AWG Aruba
            AZM Azerbaijan
            BAM Bosnia and Herzegovina
            BBD Barbados
            BDT Bangladesh
            BEF Belgium
            BGL Bulgaria
            BHD Bahrain
            BIF Burundi
            BMD Bermuda
            BND Brunei Darussalam
            BOB Bolivia
            BRL Brazil
            BSD Bahamas
            BTN Bhutan
            BWP Botswana
            BYR Belarus
            BZD Belize
            CAD Canada
            CDF Congo/Kinshasa
            CHF Switzerland
            CLP Chile
            CNY China
            COP Colombia
            CRC Costa Rica
            CUP Cuba
            CVE Cape Verde
            CYP Cyprus
            CZK Czech Republic
            DEM Germany
            DJF Djibouti
            DKK Denmark
            DOP Dominican Republic
            DZD Algeria
            EEK Estonia
            EGP Egypt
            ERN Eritrea
            ESP Spain
            ETB Ethiopia
            EUR Euro Member Countries
            FIM Finland
            FJD Fiji
            FKP Falkland Islands (Malvinas)
            FRF France
            GBP United Kingdom
            GEL Georgia
            GGP Guernsey
            GHC Ghana
            GIP Gibraltar
            GMD Gambia
            GNF Guinea
            GRD Greece
            GTQ Guatemala
            GYD Guyana
            HKD Hong Kong
            HNL Honduras
            HRK Croatia
            HTG Haiti
            HUF Hungary
            IDR Indonesia
            IEP Ireland (Eire)
            ILS Israel
            IMP Isle of Man
            INR India
            IQD Iraq
            IRR Iran
            ISK Iceland
            ITL Italy
            JEP Jersey
            JMD Jamaica
            JOD Jordan
            JPY Japan
            KES Kenya
            KGS Kyrgyzstan
            KHR Cambodia
            KMF Comoros
            KPW Korea
            KWD Kuwait
            KYD Cayman Islands
            KZT Kazakstan
            LAK Laos
            LBP Lebanon
            LKR Sri Lanka
            LRD Liberia
            LSL Lesotho
            LTL Lithuania
            LUF Luxembourg
            LVL Latvia
            LYD Libya
            MAD Morocco
            MDL Moldova
            MGF Madagascar
            MKD Macedonia
            MMK Myanmar (Burma)
            MNT Mongolia
            MOP Macau
            MRO Mauritania
            MTL Malta
            MUR Mauritius
            MVR Maldives (Maldive Islands)
            MWK Malawi
            MXN Mexico
            MYR Malaysia
            MZM Mozambique
            NAD Namibia
            NGN Nigeria
            NIO Nicaragua
            NLG Netherlands (Holland)
            NOK Norway
            NPR Nepal
            NZD New Zealand
            OMR Oman
            PAB Panama
            PEN Peru
            PGK Papua New Guinea
            PHP Philippines
            PKR Pakistan
            PLN Poland
            PTE Portugal
            PYG Paraguay
            QAR Qatar
            ROL Romania
            RUR Russia
            RWF Rwanda
            SAR Saudi Arabia
            SBD Solomon Islands
            SCR Seychelles
            SDD Sudan
            SEK Sweden
            SGD Singapore
            SHP Saint Helena
            SIT Slovenia
            SKK Slovakia
            SLL Sierra Leone
            SOS Somalia
            SPL Seborga
            SRG Suriname
            STD São Tome and Principe
            SVC El Salvador
            SYP Syria
            SZL Swaziland
            THB Thailand
            TJR Tajikistan
            TMM Turkmenistan
            TND Tunisia
            TOP Tonga
            TRL Turkey
            TTD Trinidad and Tobago
            TVD Tuvalu
            TWD Taiwan
            TZS Tanzania
            UAH Ukraine
            UGX Uganda
            USD United States of America
            UYU Uruguay
            UZS Uzbekistan
            VAL Vatican City
            VEB Venezuela
            VND Viet Nam
            VUV Vanuatu
            WST Samoa
            XAF Communauté Financière Africaine
            XAG Silver
            XAU Gold
            XCD East Caribbean
            XDR International Monetary Fund
            XPD Palladium
            XPF Comptoirs Français du Pacifique
            XPT Platinum
            YER Yemen
            YUM Yugoslavia
            ZAR South Africa
            ZMK Zambia
            ZWD Zimbabwe

        */

        return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
    }

    /**
     * @param string $languagecode
     * @param bool   $casesensitive
     *
     * @return string
     */
    public static function LanguageLookup($languagecode, $casesensitive=false) {

        if (!$casesensitive) {
            $languagecode = strtolower($languagecode);
        }

        // http://www.id3.org/id3v2.4.0-structure.txt
        // [4.   ID3v2 frame overview]
        // The three byte language field, present in several frames, is used to
        // describe the language of the frame's content, according to ISO-639-2
        // [ISO-639-2]. The language should be represented in lower case. If the
        // language is not known the string "XXX" should be used.


        // ISO 639-2 - http://www.id3.org/iso639-2.html

        $begin = __LINE__;

        /** This is not a comment!

            XXX unknown
            xxx unknown
            aar Afar
            abk Abkhazian
            ace Achinese
            ach Acoli
            ada Adangme
            afa Afro-Asiatic (Other)
            afh Afrihili
            afr Afrikaans
            aka Akan
            akk Akkadian
            alb Albanian
            ale Aleut
            alg Algonquian Languages
            amh Amharic
            ang English, Old (ca. 450-1100)
            apa Apache Languages
            ara Arabic
            arc Aramaic
            arm Armenian
            arn Araucanian
            arp Arapaho
            art Artificial (Other)
            arw Arawak
            asm Assamese
            ath Athapascan Languages
            ava Avaric
            ave Avestan
            awa Awadhi
            aym Aymara
            aze Azerbaijani
            bad Banda
            bai Bamileke Languages
            bak Bashkir
            bal Baluchi
            bam Bambara
            ban Balinese
            baq Basque
            bas Basa
            bat Baltic (Other)
            bej Beja
            bel Byelorussian
            bem Bemba
            ben Bengali
            ber Berber (Other)
            bho Bhojpuri
            bih Bihari
            bik Bikol
            bin Bini
            bis Bislama
            bla Siksika
            bnt Bantu (Other)
            bod Tibetan
            bra Braj
            bre Breton
            bua Buriat
            bug Buginese
            bul Bulgarian
            bur Burmese
            cad Caddo
            cai Central American Indian (Other)
            car Carib
            cat Catalan
            cau Caucasian (Other)
            ceb Cebuano
            cel Celtic (Other)
            ces Czech
            cha Chamorro
            chb Chibcha
            che Chechen
            chg Chagatai
            chi Chinese
            chm Mari
            chn Chinook jargon
            cho Choctaw
            chr Cherokee
            chu Church Slavic
            chv Chuvash
            chy Cheyenne
            cop Coptic
            cor Cornish
            cos Corsican
            cpe Creoles and Pidgins, English-based (Other)
            cpf Creoles and Pidgins, French-based (Other)
            cpp Creoles and Pidgins, Portuguese-based (Other)
            cre Cree
            crp Creoles and Pidgins (Other)
            cus Cushitic (Other)
            cym Welsh
            cze Czech
            dak Dakota
            dan Danish
            del Delaware
            deu German
            din Dinka
            div Divehi
            doi Dogri
            dra Dravidian (Other)
            dua Duala
            dum Dutch, Middle (ca. 1050-1350)
            dut Dutch
            dyu Dyula
            dzo Dzongkha
            efi Efik
            egy Egyptian (Ancient)
            eka Ekajuk
            ell Greek, Modern (1453-)
            elx Elamite
            eng English
            enm English, Middle (ca. 1100-1500)
            epo Esperanto
            esk Eskimo (Other)
            esl Spanish
            est Estonian
            eus Basque
            ewe Ewe
            ewo Ewondo
            fan Fang
            fao Faroese
            fas Persian
            fat Fanti
            fij Fijian
            fin Finnish
            fiu Finno-Ugrian (Other)
            fon Fon
            fra French
            fre French
            frm French, Middle (ca. 1400-1600)
            fro French, Old (842- ca. 1400)
            fry Frisian
            ful Fulah
            gaa Ga
            gae Gaelic (Scots)
            gai Irish
            gay Gayo
            gdh Gaelic (Scots)
            gem Germanic (Other)
            geo Georgian
            ger German
            gez Geez
            gil Gilbertese
            glg Gallegan
            gmh German, Middle High (ca. 1050-1500)
            goh German, Old High (ca. 750-1050)
            gon Gondi
            got Gothic
            grb Grebo
            grc Greek, Ancient (to 1453)
            gre Greek, Modern (1453-)
            grn Guarani
            guj Gujarati
            hai Haida
            hau Hausa
            haw Hawaiian
            heb Hebrew
            her Herero
            hil Hiligaynon
            him Himachali
            hin Hindi
            hmo Hiri Motu
            hun Hungarian
            hup Hupa
            hye Armenian
            iba Iban
            ibo Igbo
            ice Icelandic
            ijo Ijo
            iku Inuktitut
            ilo Iloko
            ina Interlingua (International Auxiliary language Association)
            inc Indic (Other)
            ind Indonesian
            ine Indo-European (Other)
            ine Interlingue
            ipk Inupiak
            ira Iranian (Other)
            iri Irish
            iro Iroquoian uages
            isl Icelandic
            ita Italian
            jav Javanese
            jaw Javanese
            jpn Japanese
            jpr Judeo-Persian
            jrb Judeo-Arabic
            kaa Kara-Kalpak
            kab Kabyle
            kac Kachin
            kal Greenlandic
            kam Kamba
            kan Kannada
            kar Karen
            kas Kashmiri
            kat Georgian
            kau Kanuri
            kaw Kawi
            kaz Kazakh
            kha Khasi
            khi Khoisan (Other)
            khm Khmer
            kho Khotanese
            kik Kikuyu
            kin Kinyarwanda
            kir Kirghiz
            kok Konkani
            kom Komi
            kon Kongo
            kor Korean
            kpe Kpelle
            kro Kru
            kru Kurukh
            kua Kuanyama
            kum Kumyk
            kur Kurdish
            kus Kusaie
            kut Kutenai
            lad Ladino
            lah Lahnda
            lam Lamba
            lao Lao
            lat Latin
            lav Latvian
            lez Lezghian
            lin Lingala
            lit Lithuanian
            lol Mongo
            loz Lozi
            ltz Letzeburgesch
            lub Luba-Katanga
            lug Ganda
            lui Luiseno
            lun Lunda
            luo Luo (Kenya and Tanzania)
            mac Macedonian
            mad Madurese
            mag Magahi
            mah Marshall
            mai Maithili
            mak Macedonian
            mak Makasar
            mal Malayalam
            man Mandingo
            mao Maori
            map Austronesian (Other)
            mar Marathi
            mas Masai
            max Manx
            may Malay
            men Mende
            mga Irish, Middle (900 - 1200)
            mic Micmac
            min Minangkabau
            mis Miscellaneous (Other)
            mkh Mon-Kmer (Other)
            mlg Malagasy
            mlt Maltese
            mni Manipuri
            mno Manobo Languages
            moh Mohawk
            mol Moldavian
            mon Mongolian
            mos Mossi
            mri Maori
            msa Malay
            mul Multiple Languages
            mun Munda Languages
            mus Creek
            mwr Marwari
            mya Burmese
            myn Mayan Languages
            nah Aztec
            nai North American Indian (Other)
            nau Nauru
            nav Navajo
            nbl Ndebele, South
            nde Ndebele, North
            ndo Ndongo
            nep Nepali
            new Newari
            nic Niger-Kordofanian (Other)
            niu Niuean
            nla Dutch
            nno Norwegian (Nynorsk)
            non Norse, Old
            nor Norwegian
            nso Sotho, Northern
            nub Nubian Languages
            nya Nyanja
            nym Nyamwezi
            nyn Nyankole
            nyo Nyoro
            nzi Nzima
            oci Langue d'Oc (post 1500)
            oji Ojibwa
            ori Oriya
            orm Oromo
            osa Osage
            oss Ossetic
            ota Turkish, Ottoman (1500 - 1928)
            oto Otomian Languages
            paa Papuan-Australian (Other)
            pag Pangasinan
            pal Pahlavi
            pam Pampanga
            pan Panjabi
            pap Papiamento
            pau Palauan
            peo Persian, Old (ca 600 - 400 B.C.)
            per Persian
            phn Phoenician
            pli Pali
            pol Polish
            pon Ponape
            por Portuguese
            pra Prakrit uages
            pro Provencal, Old (to 1500)
            pus Pushto
            que Quechua
            raj Rajasthani
            rar Rarotongan
            roa Romance (Other)
            roh Rhaeto-Romance
            rom Romany
            ron Romanian
            rum Romanian
            run Rundi
            rus Russian
            sad Sandawe
            sag Sango
            sah Yakut
            sai South American Indian (Other)
            sal Salishan Languages
            sam Samaritan Aramaic
            san Sanskrit
            sco Scots
            scr Serbo-Croatian
            sel Selkup
            sem Semitic (Other)
            sga Irish, Old (to 900)
            shn Shan
            sid Sidamo
            sin Singhalese
            sio Siouan Languages
            sit Sino-Tibetan (Other)
            sla Slavic (Other)
            slk Slovak
            slo Slovak
            slv Slovenian
            smi Sami Languages
            smo Samoan
            sna Shona
            snd Sindhi
            sog Sogdian
            som Somali
            son Songhai
            sot Sotho, Southern
            spa Spanish
            sqi Albanian
            srd Sardinian
            srr Serer
            ssa Nilo-Saharan (Other)
            ssw Siswant
            ssw Swazi
            suk Sukuma
            sun Sudanese
            sus Susu
            sux Sumerian
            sve Swedish
            swa Swahili
            swe Swedish
            syr Syriac
            tah Tahitian
            tam Tamil
            tat Tatar
            tel Telugu
            tem Timne
            ter Tereno
            tgk Tajik
            tgl Tagalog
            tha Thai
            tib Tibetan
            tig Tigre
            tir Tigrinya
            tiv Tivi
            tli Tlingit
            tmh Tamashek
            tog Tonga (Nyasa)
            ton Tonga (Tonga Islands)
            tru Truk
            tsi Tsimshian
            tsn Tswana
            tso Tsonga
            tuk Turkmen
            tum Tumbuka
            tur Turkish
            tut Altaic (Other)
            twi Twi
            tyv Tuvinian
            uga Ugaritic
            uig Uighur
            ukr Ukrainian
            umb Umbundu
            und Undetermined
            urd Urdu
            uzb Uzbek
            vai Vai
            ven Venda
            vie Vietnamese
            vol Volapük
            vot Votic
            wak Wakashan Languages
            wal Walamo
            war Waray
            was Washo
            wel Welsh
            wen Sorbian Languages
            wol Wolof
            xho Xhosa
            yao Yao
            yap Yap
            yid Yiddish
            yor Yoruba
            zap Zapotec
            zen Zenaga
            zha Zhuang
            zho Chinese
            zul Zulu
            zun Zuni

        */

        return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public static function ETCOEventLookup($index) {
        if (($index >= 0x17) && ($index <= 0xDF)) {
            return 'reserved for future use';
        }
        if (($index >= 0xE0) && ($index <= 0xEF)) {
            return 'not predefined synch 0-F';
        }
        if (($index >= 0xF0) && ($index <= 0xFC)) {
            return 'reserved for future use';
        }

        static $EventLookup = array(
            0x00 => 'padding (has no meaning)',
            0x01 => 'end of initial silence',
            0x02 => 'intro start',
            0x03 => 'main part start',
            0x04 => 'outro start',
            0x05 => 'outro end',
            0x06 => 'verse start',
            0x07 => 'refrain start',
            0x08 => 'interlude start',
            0x09 => 'theme start',
            0x0A => 'variation start',
            0x0B => 'key change',
            0x0C => 'time change',
            0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
            0x0E => 'sustained noise',
            0x0F => 'sustained noise end',
            0x10 => 'intro end',
            0x11 => 'main part end',
            0x12 => 'verse end',
            0x13 => 'refrain end',
            0x14 => 'theme end',
            0x15 => 'profanity',
            0x16 => 'profanity end',
            0xFD => 'audio end (start of silence)',
            0xFE => 'audio file ends',
            0xFF => 'one more byte of events follows'
        );

        return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public static function SYTLContentTypeLookup($index) {
        static $SYTLContentTypeLookup = array(
            0x00 => 'other',
            0x01 => 'lyrics',
            0x02 => 'text transcription',
            0x03 => 'movement/part name', // (e.g. 'Adagio')
            0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
            0x05 => 'chord',              // (e.g. 'Bb F Fsus')
            0x06 => 'trivia/\'pop up\' information',
            0x07 => 'URLs to webpages',
            0x08 => 'URLs to images'
        );

        return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
    }

    /**
     * @param int   $index
     * @param bool $returnarray
     *
     * @return array|string
     */
    public static function APICPictureTypeLookup($index, $returnarray=false) {
        static $APICPictureTypeLookup = array(
            0x00 => 'Other',
            0x01 => '32x32 pixels \'file icon\' (PNG only)',
            0x02 => 'Other file icon',
            0x03 => 'Cover (front)',
            0x04 => 'Cover (back)',
            0x05 => 'Leaflet page',
            0x06 => 'Media (e.g. label side of CD)',
            0x07 => 'Lead artist/lead performer/soloist',
            0x08 => 'Artist/performer',
            0x09 => 'Conductor',
            0x0A => 'Band/Orchestra',
            0x0B => 'Composer',
            0x0C => 'Lyricist/text writer',
            0x0D => 'Recording Location',
            0x0E => 'During recording',
            0x0F => 'During performance',
            0x10 => 'Movie/video screen capture',
            0x11 => 'A bright coloured fish',
            0x12 => 'Illustration',
            0x13 => 'Band/artist logotype',
            0x14 => 'Publisher/Studio logotype'
        );
        if ($returnarray) {
            return $APICPictureTypeLookup;
        }
        return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public static function COMRReceivedAsLookup($index) {
        static $COMRReceivedAsLookup = array(
            0x00 => 'Other',
            0x01 => 'Standard CD album with other songs',
            0x02 => 'Compressed audio on CD',
            0x03 => 'File over the Internet',
            0x04 => 'Stream over the Internet',
            0x05 => 'As note sheets',
            0x06 => 'As note sheets in a book with other sheets',
            0x07 => 'Music on other media',
            0x08 => 'Non-musical merchandise'
        );

        return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
    }

    /**
     * @param int $index
     *
     * @return string
     */
    public static function RVA2ChannelTypeLookup($index) {
        static $RVA2ChannelTypeLookup = array(
            0x00 => 'Other',
            0x01 => 'Master volume',
            0x02 => 'Front right',
            0x03 => 'Front left',
            0x04 => 'Back right',
            0x05 => 'Back left',
            0x06 => 'Front centre',
            0x07 => 'Back centre',
            0x08 => 'Subwoofer'
        );

        return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
    }

    /**
     * @param string $framename
     *
     * @return string
     */
    public static function FrameNameLongLookup($framename) {

        $begin = __LINE__;

        /** This is not a comment!

            AENC    Audio encryption
            APIC    Attached picture
            ASPI    Audio seek point index
            BUF Recommended buffer size
            CNT Play counter
            COM Comments
            COMM    Comments
            COMR    Commercial frame
            CRA Audio encryption
            CRM Encrypted meta frame
            ENCR    Encryption method registration
            EQU Equalisation
            EQU2    Equalisation (2)
            EQUA    Equalisation
            ETC Event timing codes
            ETCO    Event timing codes
            GEO General encapsulated object
            GEOB    General encapsulated object
            GRID    Group identification registration
            IPL Involved people list
            IPLS    Involved people list
            LINK    Linked information
            LNK Linked information
            MCDI    Music CD identifier
            MCI Music CD Identifier
            MLL MPEG location lookup table
            MLLT    MPEG location lookup table
            OWNE    Ownership frame
            PCNT    Play counter
            PIC Attached picture
            POP Popularimeter
            POPM    Popularimeter
            POSS    Position synchronisation frame
            PRIV    Private frame
            RBUF    Recommended buffer size
            REV Reverb
            RVA Relative volume adjustment
            RVA2    Relative volume adjustment (2)
            RVAD    Relative volume adjustment
            RVRB    Reverb
            SEEK    Seek frame
            SIGN    Signature frame
            SLT Synchronised lyric/text
            STC Synced tempo codes
            SYLT    Synchronised lyric/text
            SYTC    Synchronised tempo codes
            TAL Album/Movie/Show title
            TALB    Album/Movie/Show title
            TBP BPM (Beats Per Minute)
            TBPM    BPM (beats per minute)
            TCM Composer
            TCMP    Part of a compilation
            TCO Content type
            TCOM    Composer
            TCON    Content type
            TCOP    Copyright message
            TCP Part of a compilation
            TCR Copyright message
            TDA Date
            TDAT    Date
            TDEN    Encoding time
            TDLY    Playlist delay
            TDOR    Original release time
            TDRC    Recording time
            TDRL    Release time
            TDTG    Tagging time
            TDY Playlist delay
            TEN Encoded by
            TENC    Encoded by
            TEXT    Lyricist/Text writer
            TFLT    File type
            TFT File type
            TIM Time
            TIME    Time
            TIPL    Involved people list
            TIT1    Content group description
            TIT2    Title/songname/content description
            TIT3    Subtitle/Description refinement
            TKE Initial key
            TKEY    Initial key
            TLA Language(s)
            TLAN    Language(s)
            TLE Length
            TLEN    Length
            TMCL    Musician credits list
            TMED    Media type
            TMOO    Mood
            TMT Media type
            TOA Original artist(s)/performer(s)
            TOAL    Original album/movie/show title
            TOF Original filename
            TOFN    Original filename
            TOL Original Lyricist(s)/text writer(s)
            TOLY    Original lyricist(s)/text writer(s)
            TOPE    Original artist(s)/performer(s)
            TOR Original release year
            TORY    Original release year
            TOT Original album/Movie/Show title
            TOWN    File owner/licensee
            TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
            TP2 Band/Orchestra/Accompaniment
            TP3 Conductor/Performer refinement
            TP4 Interpreted, remixed, or otherwise modified by
            TPA Part of a set
            TPB Publisher
            TPE1    Lead performer(s)/Soloist(s)
            TPE2    Band/orchestra/accompaniment
            TPE3    Conductor/performer refinement
            TPE4    Interpreted, remixed, or otherwise modified by
            TPOS    Part of a set
            TPRO    Produced notice
            TPUB    Publisher
            TRC ISRC (International Standard Recording Code)
            TRCK    Track number/Position in set
            TRD Recording dates
            TRDA    Recording dates
            TRK Track number/Position in set
            TRSN    Internet radio station name
            TRSO    Internet radio station owner
            TS2 Album-Artist sort order
            TSA Album sort order
            TSC Composer sort order
            TSI Size
            TSIZ    Size
            TSO2    Album-Artist sort order
            TSOA    Album sort order
            TSOC    Composer sort order
            TSOP    Performer sort order
            TSOT    Title sort order
            TSP Performer sort order
            TSRC    ISRC (international standard recording code)
            TSS Software/hardware and settings used for encoding
            TSSE    Software/Hardware and settings used for encoding
            TSST    Set subtitle
            TST Title sort order
            TT1 Content group description
            TT2 Title/Songname/Content description
            TT3 Subtitle/Description refinement
            TXT Lyricist/text writer
            TXX User defined text information frame
            TXXX    User defined text information frame
            TYE Year
            TYER    Year
            UFI Unique file identifier
            UFID    Unique file identifier
            ULT Unsynchronised lyric/text transcription
            USER    Terms of use
            USLT    Unsynchronised lyric/text transcription
            WAF Official audio file webpage
            WAR Official artist/performer webpage
            WAS Official audio source webpage
            WCM Commercial information
            WCOM    Commercial information
            WCOP    Copyright/Legal information
            WCP Copyright/Legal information
            WOAF    Official audio file webpage
            WOAR    Official artist/performer webpage
            WOAS    Official audio source webpage
            WORS    Official Internet radio station homepage
            WPAY    Payment
            WPB Publishers official webpage
            WPUB    Publishers official webpage
            WXX User defined URL link frame
            WXXX    User defined URL link frame
            TFEA    Featured Artist
            TSTU    Recording Studio
            rgad    Replay Gain Adjustment

        */

        return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');

        // Last three:
        // from Helium2 [www.helium2.com]
        // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
    }

    /**
     * @param string $framename
     *
     * @return string
     */
    public static function FrameNameShortLookup($framename) {

        $begin = __LINE__;

        /** This is not a comment!

            AENC    audio_encryption
            APIC    attached_picture
            ASPI    audio_seek_point_index
            BUF recommended_buffer_size
            CNT play_counter
            COM comment
            COMM    comment
            COMR    commercial_frame
            CRA audio_encryption
            CRM encrypted_meta_frame
            ENCR    encryption_method_registration
            EQU equalisation
            EQU2    equalisation
            EQUA    equalisation
            ETC event_timing_codes
            ETCO    event_timing_codes
            GEO general_encapsulated_object
            GEOB    general_encapsulated_object
            GRID    group_identification_registration
            IPL involved_people_list
            IPLS    involved_people_list
            LINK    linked_information
            LNK linked_information
            MCDI    music_cd_identifier
            MCI music_cd_identifier
            MLL mpeg_location_lookup_table
            MLLT    mpeg_location_lookup_table
            OWNE    ownership_frame
            PCNT    play_counter
            PIC attached_picture
            POP popularimeter
            POPM    popularimeter
            POSS    position_synchronisation_frame
            PRIV    private_frame
            RBUF    recommended_buffer_size
            REV reverb
            RVA relative_volume_adjustment
            RVA2    relative_volume_adjustment
            RVAD    relative_volume_adjustment
            RVRB    reverb
            SEEK    seek_frame
            SIGN    signature_frame
            SLT synchronised_lyric
            STC synced_tempo_codes
            SYLT    synchronised_lyric
            SYTC    synchronised_tempo_codes
            TAL album
            TALB    album
            TBP bpm
            TBPM    bpm
            TCM composer
            TCMP    part_of_a_compilation
            TCO genre
            TCOM    composer
            TCON    genre
            TCOP    copyright_message
            TCP part_of_a_compilation
            TCR copyright_message
            TDA date
            TDAT    date
            TDEN    encoding_time
            TDLY    playlist_delay
            TDOR    original_release_time
            TDRC    recording_time
            TDRL    release_time
            TDTG    tagging_time
            TDY playlist_delay
            TEN encoded_by
            TENC    encoded_by
            TEXT    lyricist
            TFLT    file_type
            TFT file_type
            TIM time
            TIME    time
            TIPL    involved_people_list
            TIT1    content_group_description
            TIT2    title
            TIT3    subtitle
            TKE initial_key
            TKEY    initial_key
            TLA language
            TLAN    language
            TLE length
            TLEN    length
            TMCL    musician_credits_list
            TMED    media_type
            TMOO    mood
            TMT media_type
            TOA original_artist
            TOAL    original_album
            TOF original_filename
            TOFN    original_filename
            TOL original_lyricist
            TOLY    original_lyricist
            TOPE    original_artist
            TOR original_year
            TORY    original_year
            TOT original_album
            TOWN    file_owner
            TP1 artist
            TP2 band
            TP3 conductor
            TP4 remixer
            TPA part_of_a_set
            TPB publisher
            TPE1    artist
            TPE2    band
            TPE3    conductor
            TPE4    remixer
            TPOS    part_of_a_set
            TPRO    produced_notice
            TPUB    publisher
            TRC isrc
            TRCK    track_number
            TRD recording_dates
            TRDA    recording_dates
            TRK track_number
            TRSN    internet_radio_station_name
            TRSO    internet_radio_station_owner
            TS2 album_artist_sort_order
            TSA album_sort_order
            TSC composer_sort_order
            TSI size
            TSIZ    size
            TSO2    album_artist_sort_order
            TSOA    album_sort_order
            TSOC    composer_sort_order
            TSOP    performer_sort_order
            TSOT    title_sort_order
            TSP performer_sort_order
            TSRC    isrc
            TSS encoder_settings
            TSSE    encoder_settings
            TSST    set_subtitle
            TST title_sort_order
            TT1 content_group_description
            TT2 title
            TT3 subtitle
            TXT lyricist
            TXX text
            TXXX    text
            TYE year
            TYER    year
            UFI unique_file_identifier
            UFID    unique_file_identifier
            ULT unsynchronised_lyric
            USER    terms_of_use
            USLT    unsynchronised_lyric
            WAF url_file
            WAR url_artist
            WAS url_source
            WCM commercial_information
            WCOM    commercial_information
            WCOP    copyright
            WCP copyright
            WOAF    url_file
            WOAR    url_artist
            WOAS    url_source
            WORS    url_station
            WPAY    url_payment
            WPB url_publisher
            WPUB    url_publisher
            WXX url_user
            WXXX    url_user
            TFEA    featured_artist
            TSTU    recording_studio
            rgad    replay_gain_adjustment

        */

        return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
    }

    /**
     * @param string $encoding
     *
     * @return string
     */
    public static function TextEncodingTerminatorLookup($encoding) {
        // http://www.id3.org/id3v2.4.0-structure.txt
        // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
        static $TextEncodingTerminatorLookup = array(
            0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
            1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
            2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
            3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
            255 => "\x00\x00"
        );
        return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
    }

    /**
     * @param int $encoding
     *
     * @return string
     */
    public static function TextEncodingNameLookup($encoding) {
        // http://www.id3.org/id3v2.4.0-structure.txt
        // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
        static $TextEncodingNameLookup = array(
            0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
            1   => 'UTF-16',     // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
            2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
            3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
            255 => 'UTF-16BE'
        );
        return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
    }

    /**
     * @param string $string
     * @param string $terminator
     *
     * @return string
     */
    public static function RemoveStringTerminator($string, $terminator) {
        // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
        // https://github.com/JamesHeinrich/getID3/issues/121
        // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
        if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
            $string = substr($string, 0, -strlen($terminator));
        }
        return $string;
    }

    /**
     * @param string $string
     *
     * @return string
     */
    public static function MakeUTF16emptyStringEmpty($string) {
        if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
            // if string only contains a BOM or terminator then make it actually an empty string
            $string = '';
        }
        return $string;
    }

    /**
     * @param string $framename
     * @param int    $id3v2majorversion
     *
     * @return bool|int
     */
    public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
        switch ($id3v2majorversion) {
            case 2:
                return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);

            case 3:
            case 4:
                return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
        }
        return false;
    }

    /**
     * @param string $numberstring
     * @param bool   $allowdecimal
     * @param bool   $allownegative
     *
     * @return bool
     */
    public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
        for ($i = 0; $i < strlen($numberstring); $i++) {
            if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
                if (($numberstring[$i] == '.') && $allowdecimal) {
                    // allowed
                } elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
                    // allowed
                } else {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * @param string $datestamp
     *
     * @return bool
     */
    public static function IsValidDateStampString($datestamp) {
        if (strlen($datestamp) != 8) {
            return false;
        }
        if (!self::IsANumber($datestamp, false)) {
            return false;
        }
        $year  = substr($datestamp, 0, 4);
        $month = substr($datestamp, 4, 2);
        $day   = substr($datestamp, 6, 2);
        if (($year == 0) || ($month == 0) || ($day == 0)) {
            return false;
        }
        if ($month > 12) {
            return false;
        }
        if ($day > 31) {
            return false;
        }
        if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
            return false;
        }
        if (($day > 29) && ($month == 2)) {
            return false;
        }
        return true;
    }

    /**
     * @param int $majorversion
     *
     * @return int
     */
    public static function ID3v2HeaderLength($majorversion) {
        return (($majorversion == 2) ? 6 : 10);
    }

    /**
     * @param string $frame_name
     *
     * @return string|false
     */
    public static function ID3v22iTunesBrokenFrameName($frame_name) {
        // iTunes (multiple versions) has been known to write ID3v2.3 style frames
        // but use ID3v2.2 frame names, right-padded using either [space] or [null]
        // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
        // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
        static $ID3v22_iTunes_BrokenFrames = array(
            'BUF' => 'RBUF', // Recommended buffer size
            'CNT' => 'PCNT', // Play counter
            'COM' => 'COMM', // Comments
            'CRA' => 'AENC', // Audio encryption
            'EQU' => 'EQUA', // Equalisation
            'ETC' => 'ETCO', // Event timing codes
            'GEO' => 'GEOB', // General encapsulated object
            'IPL' => 'IPLS', // Involved people list
            'LNK' => 'LINK', // Linked information
            'MCI' => 'MCDI', // Music CD identifier
            'MLL' => 'MLLT', // MPEG location lookup table
            'PIC' => 'APIC', // Attached picture
            'POP' => 'POPM', // Popularimeter
            'REV' => 'RVRB', // Reverb
            'RVA' => 'RVAD', // Relative volume adjustment
            'SLT' => 'SYLT', // Synchronised lyric/text
            'STC' => 'SYTC', // Synchronised tempo codes
            'TAL' => 'TALB', // Album/Movie/Show title
            'TBP' => 'TBPM', // BPM (beats per minute)
            'TCM' => 'TCOM', // Composer
            'TCO' => 'TCON', // Content type
            'TCP' => 'TCMP', // Part of a compilation
            'TCR' => 'TCOP', // Copyright message
            'TDA' => 'TDAT', // Date
            'TDY' => 'TDLY', // Playlist delay
            'TEN' => 'TENC', // Encoded by
            'TFT' => 'TFLT', // File type
            'TIM' => 'TIME', // Time
            'TKE' => 'TKEY', // Initial key
            'TLA' => 'TLAN', // Language(s)
            'TLE' => 'TLEN', // Length
            'TMT' => 'TMED', // Media type
            'TOA' => 'TOPE', // Original artist(s)/performer(s)
            'TOF' => 'TOFN', // Original filename
            'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
            'TOR' => 'TORY', // Original release year
            'TOT' => 'TOAL', // Original album/movie/show title
            'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
            'TP2' => 'TPE2', // Band/orchestra/accompaniment
            'TP3' => 'TPE3', // Conductor/performer refinement
            'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
            'TPA' => 'TPOS', // Part of a set
            'TPB' => 'TPUB', // Publisher
            'TRC' => 'TSRC', // ISRC (international standard recording code)
            'TRD' => 'TRDA', // Recording dates
            'TRK' => 'TRCK', // Track number/Position in set
            'TS2' => 'TSO2', // Album-Artist sort order
            'TSA' => 'TSOA', // Album sort order
            'TSC' => 'TSOC', // Composer sort order
            'TSI' => 'TSIZ', // Size
            'TSP' => 'TSOP', // Performer sort order
            'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
            'TST' => 'TSOT', // Title sort order
            'TT1' => 'TIT1', // Content group description
            'TT2' => 'TIT2', // Title/songname/content description
            'TT3' => 'TIT3', // Subtitle/Description refinement
            'TXT' => 'TEXT', // Lyricist/Text writer
            'TXX' => 'TXXX', // User defined text information frame
            'TYE' => 'TYER', // Year
            'UFI' => 'UFID', // Unique file identifier
            'ULT' => 'USLT', // Unsynchronised lyric/text transcription
            'WAF' => 'WOAF', // Official audio file webpage
            'WAR' => 'WOAR', // Official artist/performer webpage
            'WAS' => 'WOAS', // Official audio source webpage
            'WCM' => 'WCOM', // Commercial information
            'WCP' => 'WCOP', // Copyright/Legal information
            'WPB' => 'WPUB', // Publishers official webpage
            'WXX' => 'WXXX', // User defined URL link frame
        );
        if (strlen($frame_name) == 4) {
            if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
                if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
                    return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
                }
            }
        }
        return false;
    }

}

ZenphotoCMS 1.5.x API documentation generated by ApiGen