Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/module.audio-video.matroska.php |
— | — | @@ -0,0 +1,1748 @@ |
| 2 | +<?php |
| 3 | +///////////////////////////////////////////////////////////////// |
| 4 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 5 | +// available at http://getid3.sourceforge.net // |
| 6 | +// or http://www.getid3.org // |
| 7 | +///////////////////////////////////////////////////////////////// |
| 8 | +// See readme.txt for more details // |
| 9 | +///////////////////////////////////////////////////////////////// |
| 10 | +// // |
| 11 | +// module.audio-video.matriska.php // |
| 12 | +// module for analyzing Matroska containers // |
| 13 | +// dependencies: NONE // |
| 14 | +// /// |
| 15 | +///////////////////////////////////////////////////////////////// |
| 16 | + |
| 17 | + |
| 18 | +define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. |
| 19 | +define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. |
| 20 | +define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found here. |
| 21 | +define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. |
| 22 | +define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. |
| 23 | +define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. |
| 24 | +define('EBML_ID_ATTACHMENTS', 0x0941A469); // [19][41][A4][69] -- Contain attached files. |
| 25 | +define('EBML_ID_EBML', 0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this. |
| 26 | +define('EBML_ID_CUES', 0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment. |
| 27 | +define('EBML_ID_CLUSTER', 0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure. |
| 28 | +define('EBML_ID_LANGUAGE', 0x02B59C); // [22][B5][9C] -- Specifies the language of the track in the Matroska languages form. |
| 29 | +define('EBML_ID_TRACKTIMECODESCALE', 0x03314F); // [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs). |
| 30 | +define('EBML_ID_DEFAULTDURATION', 0x03E383); // [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame. |
| 31 | +define('EBML_ID_CODECNAME', 0x058688); // [25][86][88] -- A human-readable string specifying the codec. |
| 32 | +define('EBML_ID_CODECDOWNLOADURL', 0x06B240); // [26][B2][40] -- A URL to download about the codec used. |
| 33 | +define('EBML_ID_TIMECODESCALE', 0x0AD7B1); // [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds). |
| 34 | +define('EBML_ID_COLOURSPACE', 0x0EB524); // [2E][B5][24] -- Same value as in AVI (32 bits). |
| 35 | +define('EBML_ID_GAMMAVALUE', 0x0FB523); // [2F][B5][23] -- Gamma Value. |
| 36 | +define('EBML_ID_CODECSETTINGS', 0x1A9697); // [3A][96][97] -- A string describing the encoding setting used. |
| 37 | +define('EBML_ID_CODECINFOURL', 0x1B4040); // [3B][40][40] -- A URL to find information about the codec used. |
| 38 | +define('EBML_ID_PREVFILENAME', 0x1C83AB); // [3C][83][AB] -- An escaped filename corresponding to the previous segment. |
| 39 | +define('EBML_ID_PREVUID', 0x1CB923); // [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits). |
| 40 | +define('EBML_ID_NEXTFILENAME', 0x1E83BB); // [3E][83][BB] -- An escaped filename corresponding to the next segment. |
| 41 | +define('EBML_ID_NEXTUID', 0x1EB923); // [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits). |
| 42 | +define('EBML_ID_CONTENTCOMPALGO', 0x0254); // [42][54] -- The compression algorithm used. Algorithms that have been specified so far are: |
| 43 | +define('EBML_ID_CONTENTCOMPSETTINGS', 0x0255); // [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track. |
| 44 | +define('EBML_ID_DOCTYPE', 0x0282); // [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case). |
| 45 | +define('EBML_ID_DOCTYPEREADVERSION', 0x0285); // [42][85] -- The minimum DocType version an interpreter has to support to read this file. |
| 46 | +define('EBML_ID_EBMLVERSION', 0x0286); // [42][86] -- The version of EBML parser used to create the file. |
| 47 | +define('EBML_ID_DOCTYPEVERSION', 0x0287); // [42][87] -- The version of DocType interpreter used to create the file. |
| 48 | +define('EBML_ID_EBMLMAXIDLENGTH', 0x02F2); // [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska). |
| 49 | +define('EBML_ID_EBMLMAXSIZELENGTH', 0x02F3); // [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid. |
| 50 | +define('EBML_ID_EBMLREADVERSION', 0x02F7); // [42][F7] -- The minimum EBML version a parser has to support to read this file. |
| 51 | +define('EBML_ID_CHAPLANGUAGE', 0x037C); // [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form. |
| 52 | +define('EBML_ID_CHAPCOUNTRY', 0x037E); // [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains. |
| 53 | +define('EBML_ID_SEGMENTFAMILY', 0x0444); // [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits). |
| 54 | +define('EBML_ID_DATEUTC', 0x0461); // [44][61] -- Date of the origin of timecode (value 0), i.e. production date. |
| 55 | +define('EBML_ID_TAGLANGUAGE', 0x047A); // [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form. |
| 56 | +define('EBML_ID_TAGDEFAULT', 0x0484); // [44][84] -- Indication to know if this is the default/original language to use for the given tag. |
| 57 | +define('EBML_ID_TAGBINARY', 0x0485); // [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString. |
| 58 | +define('EBML_ID_TAGSTRING', 0x0487); // [44][87] -- The value of the Tag. |
| 59 | +define('EBML_ID_DURATION', 0x0489); // [44][89] -- Duration of the segment (based on TimecodeScale). |
| 60 | +define('EBML_ID_CHAPPROCESSPRIVATE', 0x050D); // [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent. |
| 61 | +define('EBML_ID_CHAPTERFLAGENABLED', 0x0598); // [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter. |
| 62 | +define('EBML_ID_TAGNAME', 0x05A3); // [45][A3] -- The name of the Tag that is going to be stored. |
| 63 | +define('EBML_ID_EDITIONENTRY', 0x05B9); // [45][B9] -- Contains all information about a segment edition. |
| 64 | +define('EBML_ID_EDITIONUID', 0x05BC); // [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition. |
| 65 | +define('EBML_ID_EDITIONFLAGHIDDEN', 0x05BD); // [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks). |
| 66 | +define('EBML_ID_EDITIONFLAGDEFAULT', 0x05DB); // [45][DB] -- If a flag is set (1) the edition should be used as the default one. |
| 67 | +define('EBML_ID_EDITIONFLAGORDERED', 0x05DD); // [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced. |
| 68 | +define('EBML_ID_FILEDATA', 0x065C); // [46][5C] -- The data of the file. |
| 69 | +define('EBML_ID_FILEMIMETYPE', 0x0660); // [46][60] -- MIME type of the file. |
| 70 | +define('EBML_ID_FILENAME', 0x066E); // [46][6E] -- Filename of the attached file. |
| 71 | +define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- A binary value that a track/codec can refer to when the attachment is needed. |
| 72 | +define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file. |
| 73 | +define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible. |
| 74 | +define('EBML_ID_CONTENTENCALGO', 0x07E1); // [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values: |
| 75 | +define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the the data was encrypted with. |
| 76 | +define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents. |
| 77 | +define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with. |
| 78 | +define('EBML_ID_CONTENTSIGALGO', 0x07E5); // [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: |
| 79 | +define('EBML_ID_CONTENTSIGHASHALGO', 0x07E6); // [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: |
| 80 | +define('EBML_ID_MUXINGAPP', 0x0D80); // [4D][80] -- Muxing application or library ("libmatroska-0.4.3"). |
| 81 | +define('EBML_ID_SEEK', 0x0DBB); // [4D][BB] -- Contains a single seek entry to an EBML element. |
| 82 | +define('EBML_ID_CONTENTENCODINGORDER', 0x1031); // [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment. |
| 83 | +define('EBML_ID_CONTENTENCODINGSCOPE', 0x1032); // [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values: |
| 84 | +define('EBML_ID_CONTENTENCODINGTYPE', 0x1033); // [50][33] -- A value describing what kind of transformation has been done. Possible values: |
| 85 | +define('EBML_ID_CONTENTCOMPRESSION', 0x1034); // [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking. |
| 86 | +define('EBML_ID_CONTENTENCRYPTION', 0x1035); // [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise. |
| 87 | +define('EBML_ID_CUEREFNUMBER', 0x135F); // [53][5F] -- Number of the referenced Block of Track X in the specified Cluster. |
| 88 | +define('EBML_ID_NAME', 0x136E); // [53][6E] -- A human-readable track name. |
| 89 | +define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- Number of the Block in the specified Cluster. |
| 90 | +define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track. |
| 91 | +define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name. |
| 92 | +define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element). |
| 93 | +define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode on 2 bits (0: mono, 1: right eye, 2: left eye, 3: both eyes). |
| 94 | +define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content). |
| 95 | +define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display. |
| 96 | +define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches). |
| 97 | +define('EBML_ID_ASPECTRATIOTYPE', 0x14B3); // [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed). |
| 98 | +define('EBML_ID_DISPLAYHEIGHT', 0x14BA); // [54][BA] -- Height of the video frames to display. |
| 99 | +define('EBML_ID_PIXELCROPTOP', 0x14BB); // [54][BB] -- The number of video pixels to remove at the top of the image. |
| 100 | +define('EBML_ID_PIXELCROPLEFT', 0x14CC); // [54][CC] -- The number of video pixels to remove on the left of the image. |
| 101 | +define('EBML_ID_PIXELCROPRIGHT', 0x14DD); // [54][DD] -- The number of video pixels to remove on the right of the image. |
| 102 | +define('EBML_ID_FLAGFORCED', 0x15AA); // [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind. |
| 103 | +define('EBML_ID_MAXBLOCKADDITIONID', 0x15EE); // [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track. |
| 104 | +define('EBML_ID_WRITINGAPP', 0x1741); // [57][41] -- Writing application ("mkvmerge-0.3.3"). |
| 105 | +define('EBML_ID_CLUSTERSILENTTRACKS', 0x1854); // [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use. |
| 106 | +define('EBML_ID_CLUSTERSILENTTRACKNUMBER', 0x18D7); // [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster. |
| 107 | +define('EBML_ID_ATTACHEDFILE', 0x21A7); // [61][A7] -- An attached file. |
| 108 | +define('EBML_ID_CONTENTENCODING', 0x2240); // [62][40] -- Settings for one content encoding like compression or encryption. |
| 109 | +define('EBML_ID_BITDEPTH', 0x2264); // [62][64] -- Bits per sample, mostly used for PCM. |
| 110 | +define('EBML_ID_CODECPRIVATE', 0x23A2); // [63][A2] -- Private data only known to the codec. |
| 111 | +define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment. |
| 112 | +define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. |
| 113 | +define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. |
| 114 | +define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. |
| 115 | +define('EBML_ID_ATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. |
| 116 | +define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. |
| 117 | +define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). |
| 118 | +define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. |
| 119 | +define('EBML_ID_TRACKTRANSLATETRACKID', 0x26A5); // [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used. |
| 120 | +define('EBML_ID_TRACKTRANSLATECODEC', 0x26BF); // [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). |
| 121 | +define('EBML_ID_TRACKTRANSLATEEDITIONUID', 0x26FC); // [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment. |
| 122 | +define('EBML_ID_SIMPLETAG', 0x27C8); // [67][C8] -- Contains general information about the target. |
| 123 | +define('EBML_ID_TARGETTYPEVALUE', 0x28CA); // [68][CA] -- A number to indicate the logical level of the target (see TargetType). |
| 124 | +define('EBML_ID_CHAPPROCESSCOMMAND', 0x2911); // [69][11] -- Contains all the commands associated to the Atom. |
| 125 | +define('EBML_ID_CHAPPROCESSTIME', 0x2922); // [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter). |
| 126 | +define('EBML_ID_CHAPTERTRANSLATE', 0x2924); // [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment. |
| 127 | +define('EBML_ID_CHAPPROCESSDATA', 0x2933); // [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands. |
| 128 | +define('EBML_ID_CHAPPROCESS', 0x2944); // [69][44] -- Contains all the commands associated to the Atom. |
| 129 | +define('EBML_ID_CHAPPROCESSCODECID', 0x2955); // [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later. |
| 130 | +define('EBML_ID_CHAPTERTRANSLATEID', 0x29A5); // [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used. |
| 131 | +define('EBML_ID_CHAPTERTRANSLATECODEC', 0x29BF); // [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). |
| 132 | +define('EBML_ID_CHAPTERTRANSLATEEDITIONUID', 0x29FC); // [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment. |
| 133 | +define('EBML_ID_CONTENTENCODINGS', 0x2D80); // [6D][80] -- Settings for several content encoding mechanisms like compression or encryption. |
| 134 | +define('EBML_ID_MINCACHE', 0x2DE7); // [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used. |
| 135 | +define('EBML_ID_MAXCACHE', 0x2DF8); // [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed. |
| 136 | +define('EBML_ID_CHAPTERSEGMENTUID', 0x2E67); // [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used. |
| 137 | +define('EBML_ID_CHAPTERSEGMENTEDITIONUID', 0x2EBC); // [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID. |
| 138 | +define('EBML_ID_TRACKOVERLAY', 0x2FAB); // [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc. |
| 139 | +define('EBML_ID_TAG', 0x3373); // [73][73] -- Element containing elements specific to Tracks/Chapters. |
| 140 | +define('EBML_ID_SEGMENTFILENAME', 0x3384); // [73][84] -- A filename corresponding to this segment. |
| 141 | +define('EBML_ID_SEGMENTUID', 0x33A4); // [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits). |
| 142 | +define('EBML_ID_CHAPTERUID', 0x33C4); // [73][C4] -- A unique ID to identify the Chapter. |
| 143 | +define('EBML_ID_TRACKUID', 0x33C5); // [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file. |
| 144 | +define('EBML_ID_ATTACHMENTLINK', 0x3446); // [74][46] -- The UID of an attachment that is used by this codec. |
| 145 | +define('EBML_ID_CLUSTERBLOCKADDITIONS', 0x35A1); // [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data. |
| 146 | +define('EBML_ID_CHANNELPOSITIONS', 0x347B); // [7D][7B] -- Table of horizontal angles for each successive channel, see appendix. |
| 147 | +define('EBML_ID_OUTPUTSAMPLINGFREQUENCY', 0x38B5); // [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques). |
| 148 | +define('EBML_ID_TITLE', 0x3BA9); // [7B][A9] -- General name of the segment. |
| 149 | +define('EBML_ID_CHAPTERDISPLAY', 0x00); // [80] -- Contains all possible strings to use for the chapter display. |
| 150 | +define('EBML_ID_TRACKTYPE', 0x03); // [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control). |
| 151 | +define('EBML_ID_CHAPSTRING', 0x05); // [85] -- Contains the string to use as the chapter atom. |
| 152 | +define('EBML_ID_CODECID', 0x06); // [86] -- An ID corresponding to the codec, see the codec page for more info. |
| 153 | +define('EBML_ID_FLAGDEFAULT', 0x08); // [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference. |
| 154 | +define('EBML_ID_CHAPTERTRACKNUMBER', 0x09); // [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks. |
| 155 | +define('EBML_ID_CLUSTERSLICES', 0x0E); // [8E] -- Contains slices description. |
| 156 | +define('EBML_ID_CHAPTERTRACK', 0x0F); // [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply |
| 157 | +define('EBML_ID_CHAPTERTIMESTART', 0x11); // [91] -- Timecode of the start of Chapter (not scaled). |
| 158 | +define('EBML_ID_CHAPTERTIMEEND', 0x12); // [92] -- Timecode of the end of Chapter (timecode excluded, not scaled). |
| 159 | +define('EBML_ID_CUEREFTIME', 0x16); // [96] -- Timecode of the referenced Block. |
| 160 | +define('EBML_ID_CUEREFCLUSTER', 0x17); // [97] -- Position of the Cluster containing the referenced Block. |
| 161 | +define('EBML_ID_CHAPTERFLAGHIDDEN', 0x18); // [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks). |
| 162 | +define('EBML_ID_FLAGINTERLACED', 0x1A); // [9A] -- Set if the video is interlaced. |
| 163 | +define('EBML_ID_CLUSTERBLOCKDURATION', 0x1B); // [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks. |
| 164 | +define('EBML_ID_FLAGLACING', 0x1C); // [9C] -- Set if the track may contain blocks using lacing. |
| 165 | +define('EBML_ID_CHANNELS', 0x1F); // [9F] -- Numbers of channels in the track. |
| 166 | +define('EBML_ID_CLUSTERBLOCKGROUP', 0x20); // [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock. |
| 167 | +define('EBML_ID_CLUSTERBLOCK', 0x21); // [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode. |
| 168 | +define('EBML_ID_CLUSTERBLOCKVIRTUAL', 0x22); // [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order. |
| 169 | +define('EBML_ID_CLUSTERSIMPLEBLOCK', 0x23); // [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed. |
| 170 | +define('EBML_ID_CLUSTERCODECSTATE', 0x24); // [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry. |
| 171 | +define('EBML_ID_CLUSTERBLOCKADDITIONAL', 0x25); // [A5] -- Interpreted by the codec as it wishes (using the BlockAddID). |
| 172 | +define('EBML_ID_CLUSTERBLOCKMORE', 0x26); // [A6] -- Contain the BlockAdditional and some parameters. |
| 173 | +define('EBML_ID_CLUSTERPOSITION', 0x27); // [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams. |
| 174 | +define('EBML_ID_CODECDECODEALL', 0x2A); // [AA] -- The codec can decode potentially damaged data. |
| 175 | +define('EBML_ID_CLUSTERPREVSIZE', 0x2B); // [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing. |
| 176 | +define('EBML_ID_TRACKENTRY', 0x2E); // [AE] -- Describes a track with all elements. |
| 177 | +define('EBML_ID_CLUSTERENCRYPTEDBLOCK', 0x2F); // [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed). |
| 178 | +define('EBML_ID_PIXELWIDTH', 0x30); // [B0] -- Width of the encoded video frames in pixels. |
| 179 | +define('EBML_ID_CUETIME', 0x33); // [B3] -- Absolute timecode according to the segment time base. |
| 180 | +define('EBML_ID_SAMPLINGFREQUENCY', 0x35); // [B5] -- Sampling frequency in Hz. |
| 181 | +define('EBML_ID_CHAPTERATOM', 0x36); // [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks). |
| 182 | +define('EBML_ID_CUETRACKPOSITIONS', 0x37); // [B7] -- Contain positions for different tracks corresponding to the timecode. |
| 183 | +define('EBML_ID_FLAGENABLED', 0x39); // [B9] -- Set if the track is used. |
| 184 | +define('EBML_ID_PIXELHEIGHT', 0x3A); // [BA] -- Height of the encoded video frames in pixels. |
| 185 | +define('EBML_ID_CUEPOINT', 0x3B); // [BB] -- Contains all information relative to a seek point in the segment. |
| 186 | +define('EBML_ID_CRC32', 0x3F); // [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32. |
| 187 | +define('EBML_ID_CLUSTERBLOCKADDITIONID', 0x4B); // [CB] -- The ID of the BlockAdditional element (0 is the main Block). |
| 188 | +define('EBML_ID_CLUSTERLACENUMBER', 0x4C); // [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. |
| 189 | +define('EBML_ID_CLUSTERFRAMENUMBER', 0x4D); // [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame). |
| 190 | +define('EBML_ID_CLUSTERDELAY', 0x4E); // [CE] -- The (scaled) delay to apply to the element. |
| 191 | +define('EBML_ID_CLUSTERDURATION', 0x4F); // [CF] -- The (scaled) duration to apply to the element. |
| 192 | +define('EBML_ID_TRACKNUMBER', 0x57); // [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number). |
| 193 | +define('EBML_ID_CUEREFERENCE', 0x5B); // [DB] -- The Clusters containing the required referenced Blocks. |
| 194 | +define('EBML_ID_VIDEO', 0x60); // [E0] -- Video settings. |
| 195 | +define('EBML_ID_AUDIO', 0x61); // [E1] -- Audio settings. |
| 196 | +define('EBML_ID_CLUSTERTIMESLICE', 0x68); // [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. |
| 197 | +define('EBML_ID_CUECODECSTATE', 0x6A); // [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry. |
| 198 | +define('EBML_ID_CUEREFCODECSTATE', 0x6B); // [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry. |
| 199 | +define('EBML_ID_VOID', 0x6C); // [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use. |
| 200 | +define('EBML_ID_CLUSTERTIMECODE', 0x67); // [E7] -- Absolute timecode of the cluster (based on TimecodeScale). |
| 201 | +define('EBML_ID_CLUSTERBLOCKADDID', 0x6E); // [EE] -- An ID to identify the BlockAdditional level. |
| 202 | +define('EBML_ID_CUECLUSTERPOSITION', 0x71); // [F1] -- The position of the Cluster containing the required Block. |
| 203 | +define('EBML_ID_CUETRACK', 0x77); // [F7] -- The track for which a position is given. |
| 204 | +define('EBML_ID_CLUSTERREFERENCEPRIORITY', 0x7A); // [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced. |
| 205 | +define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to. |
| 206 | +define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. |
| 207 | + |
| 208 | + |
| 209 | +class getid3_matroska |
| 210 | +{ |
| 211 | + var $read_buffer_size = 32768; // size of read buffer, 32kB is default |
| 212 | + var $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful |
| 213 | + var $warnings = array(); |
| 214 | + |
| 215 | + function getid3_matroska(&$fd, &$ThisFileInfo) { |
| 216 | + |
| 217 | + // http://www.matroska.org/technical/specs/index.html#EBMLBasics |
| 218 | + $offset = $ThisFileInfo['avdataoffset']; |
| 219 | + $EBMLdata = ''; |
| 220 | + $EBMLdata_offset = $offset; |
| 221 | + |
| 222 | + if ($ThisFileInfo['avdataend'] > 2147483648) { |
| 223 | + $this->warnings[] = 'This version of getID3() may or may not correctly handle Matroska files larger than 2GB'; |
| 224 | + } |
| 225 | + |
| 226 | + while ($offset < $ThisFileInfo['avdataend']) { |
| 227 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 228 | + |
| 229 | + $top_element_offset = $offset; |
| 230 | + $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 231 | + $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 232 | + if ($top_element_length === false) { |
| 233 | + $this->warnings[] = 'invalid chunk length at '.$top_element_offset; |
| 234 | + $offset = pow(2, 63); |
| 235 | + break; |
| 236 | + } |
| 237 | + $top_element_endoffset = $offset + $top_element_length; |
| 238 | + switch ($top_element_id) { |
| 239 | + case EBML_ID_EBML: |
| 240 | + $ThisFileInfo['fileformat'] = 'matroska'; |
| 241 | + $ThisFileInfo['matroska']['header']['offset'] = $top_element_offset; |
| 242 | + $ThisFileInfo['matroska']['header']['length'] = $top_element_length; |
| 243 | + |
| 244 | + while ($offset < $top_element_endoffset) { |
| 245 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 246 | + $element_data = array(); |
| 247 | + $element_data_offset = $offset; |
| 248 | + $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 249 | + $element_data['id_name'] = $this->EBMLidName($element_data['id']); |
| 250 | + $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 251 | + $end_offset = $offset + $element_data['length']; |
| 252 | + |
| 253 | + switch ($element_data['id']) { |
| 254 | + case EBML_ID_VOID: // padding, ignore |
| 255 | + break; |
| 256 | + case EBML_ID_EBMLVERSION: |
| 257 | + case EBML_ID_EBMLREADVERSION: |
| 258 | + case EBML_ID_EBMLMAXIDLENGTH: |
| 259 | + case EBML_ID_EBMLMAXSIZELENGTH: |
| 260 | + case EBML_ID_DOCTYPEVERSION: |
| 261 | + case EBML_ID_DOCTYPEREADVERSION: |
| 262 | + $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); |
| 263 | + break; |
| 264 | + case EBML_ID_DOCTYPE: |
| 265 | + $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), "\x00"); |
| 266 | + break; |
| 267 | + default: |
| 268 | + $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data_offset; |
| 269 | + break; |
| 270 | + } |
| 271 | + $offset = $end_offset; |
| 272 | + $ThisFileInfo['matroska']['header']['elements'][] = $element_data; |
| 273 | + } |
| 274 | + break; |
| 275 | + |
| 276 | + |
| 277 | + case EBML_ID_SEGMENT: |
| 278 | + $ThisFileInfo['matroska']['segment'][0]['offset'] = $top_element_offset; |
| 279 | + $ThisFileInfo['matroska']['segment'][0]['length'] = $top_element_length; |
| 280 | + |
| 281 | + $segment_key = -1; |
| 282 | + while ($offset < $ThisFileInfo['avdataend']) { |
| 283 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 284 | + |
| 285 | + $element_data = array(); |
| 286 | + $element_data['offset'] = $offset; |
| 287 | + $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 288 | + $element_data['id_name'] = $this->EBMLidName($element_data['id']); |
| 289 | + $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 290 | + if ($element_data['length'] === false) { |
| 291 | + $this->warnings[] = 'invalid chunk length at '.$element_data['offset']; |
| 292 | + //$offset = pow(2, 63); |
| 293 | + $offset = $ThisFileInfo['avdataend']; |
| 294 | + break; |
| 295 | + } |
| 296 | + $element_end = $offset + $element_data['length']; |
| 297 | + switch ($element_data['id']) { |
| 298 | + //case EBML_ID_CLUSTER: |
| 299 | + // // too many cluster entries, probably not useful |
| 300 | + // break; |
| 301 | + case false: |
| 302 | + $this->warnings[] = 'invalid ID at '.$element_data['offset']; |
| 303 | + $offset = $element_end; |
| 304 | + continue 3; |
| 305 | + default: |
| 306 | + $ThisFileInfo['matroska']['segments'][] = $element_data; |
| 307 | + break; |
| 308 | + } |
| 309 | + $segment_key++; |
| 310 | + |
| 311 | + switch ($element_data['id']) { |
| 312 | + case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements |
| 313 | + while ($offset < $element_end) { |
| 314 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 315 | + $seek_entry = array(); |
| 316 | + $seek_entry['offset'] = $offset; |
| 317 | + $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 318 | + $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); |
| 319 | + $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 320 | + $seek_end_offset = $offset + $seek_entry['length']; |
| 321 | + switch ($seek_entry['id']) { |
| 322 | + case EBML_ID_SEEK: // Contains a single seek entry to an EBML element |
| 323 | + while ($offset < $seek_end_offset) { |
| 324 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 325 | + $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 326 | + $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 327 | + $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); |
| 328 | + $offset += $length; |
| 329 | + switch ($id) { |
| 330 | + case EBML_ID_SEEKID: |
| 331 | + $dummy = 0; |
| 332 | + $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); |
| 333 | + $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); |
| 334 | + break; |
| 335 | + case EBML_ID_SEEKPOSITION: |
| 336 | + $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); |
| 337 | + break; |
| 338 | + default: |
| 339 | + $ThisFileInfo['error'][] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$id.') at '.$offset; |
| 340 | + break; |
| 341 | + } |
| 342 | + } |
| 343 | + $ThisFileInfo['matroska']['seek'][] = $seek_entry; |
| 344 | + //switch ($seek_entry['target_id']) { |
| 345 | + // case EBML_ID_CLUSTER: |
| 346 | + // // too many cluster seek points, probably not useful |
| 347 | + // break; |
| 348 | + // default: |
| 349 | + // $ThisFileInfo['matroska']['seek'][] = $seek_entry; |
| 350 | + // break; |
| 351 | + //} |
| 352 | + break; |
| 353 | + default: |
| 354 | + $this->warnings[] = 'Unhandled seekhead element ['.basename(__FILE__).':'.__LINE__.'] ('.$seek_entry['id'].') at '.$offset; |
| 355 | + break; |
| 356 | + } |
| 357 | + $offset = $seek_end_offset; |
| 358 | + } |
| 359 | + break; |
| 360 | + |
| 361 | + case EBML_ID_TRACKS: // information about all tracks in segment |
| 362 | + $ThisFileInfo['matroska']['tracks'] = $element_data; |
| 363 | + while ($offset < $element_end) { |
| 364 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 365 | + $track_entry = array(); |
| 366 | + $track_entry['offset'] = $offset; |
| 367 | + $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 368 | + $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); |
| 369 | + $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 370 | + $track_entry_endoffset = $offset + $track_entry['length']; |
| 371 | + switch ($track_entry['id']) { |
| 372 | + case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. |
| 373 | + while ($offset < $track_entry_endoffset) { |
| 374 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 375 | + $subelement_offset = $offset; |
| 376 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 377 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 378 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 379 | + $subelement_end = $offset + $subelement_length; |
| 380 | + switch ($subelement_id) { |
| 381 | + case EBML_ID_TRACKNUMBER: |
| 382 | + case EBML_ID_TRACKUID: |
| 383 | + case EBML_ID_TRACKTYPE: |
| 384 | + case EBML_ID_MINCACHE: |
| 385 | + case EBML_ID_MAXCACHE: |
| 386 | + case EBML_ID_MAXBLOCKADDITIONID: |
| 387 | + case EBML_ID_DEFAULTDURATION: // nanoseconds per frame |
| 388 | + $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 389 | + break; |
| 390 | + |
| 391 | + case EBML_ID_TRACKTIMECODESCALE: |
| 392 | + $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 393 | + break; |
| 394 | + |
| 395 | + case EBML_ID_CODECID: |
| 396 | + case EBML_ID_LANGUAGE: |
| 397 | + case EBML_ID_NAME: |
| 398 | + case EBML_ID_CODECNAME: |
| 399 | + case EBML_ID_CODECPRIVATE: |
| 400 | + $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); |
| 401 | + break; |
| 402 | + |
| 403 | + case EBML_ID_FLAGENABLED: |
| 404 | + case EBML_ID_FLAGDEFAULT: |
| 405 | + case EBML_ID_FLAGFORCED: |
| 406 | + case EBML_ID_FLAGLACING: |
| 407 | + case EBML_ID_CODECDECODEALL: |
| 408 | + $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 409 | + break; |
| 410 | + |
| 411 | + case EBML_ID_VIDEO: |
| 412 | + while ($offset < $subelement_end) { |
| 413 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 414 | + $sub_subelement_offset = $offset; |
| 415 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 416 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 417 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 418 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 419 | + switch ($sub_subelement_id) { |
| 420 | + case EBML_ID_PIXELWIDTH: |
| 421 | + case EBML_ID_PIXELHEIGHT: |
| 422 | + case EBML_ID_STEREOMODE: |
| 423 | + case EBML_ID_PIXELCROPBOTTOM: |
| 424 | + case EBML_ID_PIXELCROPTOP: |
| 425 | + case EBML_ID_PIXELCROPLEFT: |
| 426 | + case EBML_ID_PIXELCROPRIGHT: |
| 427 | + case EBML_ID_DISPLAYWIDTH: |
| 428 | + case EBML_ID_DISPLAYHEIGHT: |
| 429 | + case EBML_ID_DISPLAYUNIT: |
| 430 | + case EBML_ID_ASPECTRATIOTYPE: |
| 431 | + $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 432 | + break; |
| 433 | + case EBML_ID_FLAGINTERLACED: |
| 434 | + $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 435 | + break; |
| 436 | + case EBML_ID_GAMMAVALUE: |
| 437 | + $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 438 | + break; |
| 439 | + case EBML_ID_COLOURSPACE: |
| 440 | + $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); |
| 441 | + break; |
| 442 | + default: |
| 443 | + $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 444 | + break; |
| 445 | + } |
| 446 | + $offset = $sub_subelement_end; |
| 447 | + } |
| 448 | + |
| 449 | + if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && ($track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC') && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { |
| 450 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { |
| 451 | + $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); |
| 452 | + } else { |
| 453 | + $this->warnings[] = 'Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'; |
| 454 | + } |
| 455 | + } |
| 456 | + break; |
| 457 | + |
| 458 | + case EBML_ID_AUDIO: |
| 459 | + while ($offset < $subelement_end) { |
| 460 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 461 | + $sub_subelement_offset = $offset; |
| 462 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 463 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 464 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 465 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 466 | + switch ($sub_subelement_id) { |
| 467 | + case EBML_ID_CHANNELS: |
| 468 | + case EBML_ID_BITDEPTH: |
| 469 | + $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 470 | + break; |
| 471 | + case EBML_ID_SAMPLINGFREQUENCY: |
| 472 | + case EBML_ID_OUTPUTSAMPLINGFREQUENCY: |
| 473 | + $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 474 | + break; |
| 475 | + case EBML_ID_CHANNELPOSITIONS: |
| 476 | + $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); |
| 477 | + break; |
| 478 | + default: |
| 479 | + $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 480 | + break; |
| 481 | + } |
| 482 | + $offset = $sub_subelement_end; |
| 483 | + } |
| 484 | + break; |
| 485 | + |
| 486 | + case EBML_ID_CONTENTENCODINGS: |
| 487 | + while ($offset < $subelement_end) { |
| 488 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 489 | + $sub_subelement_offset = $offset; |
| 490 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 491 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 492 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 493 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 494 | + switch ($sub_subelement_id) { |
| 495 | + case EBML_ID_CONTENTENCODING: |
| 496 | + while ($offset < $sub_subelement_end) { |
| 497 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 498 | + $sub_sub_subelement_offset = $offset; |
| 499 | + $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 500 | + $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 501 | + $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 502 | + $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; |
| 503 | + switch ($sub_sub_subelement_id) { |
| 504 | + case EBML_ID_CONTENTENCODINGORDER: |
| 505 | + case EBML_ID_CONTENTENCODINGSCOPE: |
| 506 | + case EBML_ID_CONTENTENCODINGTYPE: |
| 507 | + $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 508 | + break; |
| 509 | + case EBML_ID_CONTENTCOMPRESSION: |
| 510 | + while ($offset < $sub_sub_subelement_end) { |
| 511 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 512 | + $sub_sub_sub_subelement_offset = $offset; |
| 513 | + $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 514 | + $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 515 | + $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 516 | + $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; |
| 517 | + switch ($sub_sub_sub_subelement_id) { |
| 518 | + case EBML_ID_CONTENTCOMPALGO: |
| 519 | + $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); |
| 520 | + break; |
| 521 | + case EBML_ID_CONTENTCOMPSETTINGS: |
| 522 | + $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); |
| 523 | + break; |
| 524 | + default: |
| 525 | + $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 526 | + break; |
| 527 | + } |
| 528 | + $offset = $sub_sub_sub_subelement_end; |
| 529 | + } |
| 530 | + break; |
| 531 | + |
| 532 | + case EBML_ID_CONTENTENCRYPTION: |
| 533 | + while ($offset < $sub_sub_subelement_end) { |
| 534 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 535 | + $sub_sub_sub_subelement_offset = $offset; |
| 536 | + $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 537 | + $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 538 | + $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 539 | + $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; |
| 540 | + switch ($sub_sub_sub_subelement_id) { |
| 541 | + case EBML_ID_CONTENTENCALGO: |
| 542 | + case EBML_ID_CONTENTSIGALGO: |
| 543 | + case EBML_ID_CONTENTSIGHASHALGO: |
| 544 | + $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); |
| 545 | + break; |
| 546 | + case EBML_ID_CONTENTENCKEYID: |
| 547 | + case EBML_ID_CONTENTSIGNATURE: |
| 548 | + case EBML_ID_CONTENTSIGKEYID: |
| 549 | + $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); |
| 550 | + break; |
| 551 | + default: |
| 552 | + $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 553 | + break; |
| 554 | + } |
| 555 | + $offset = $sub_sub_sub_subelement_end; |
| 556 | + } |
| 557 | + break; |
| 558 | + |
| 559 | + default: |
| 560 | + $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 561 | + break; |
| 562 | + } |
| 563 | + $offset = $sub_sub_subelement_end; |
| 564 | + } |
| 565 | + break; |
| 566 | + default: |
| 567 | + $this->warnings[] = 'Unhandled track.contentencodings element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 568 | + break; |
| 569 | + } |
| 570 | + $offset = $sub_subelement_end; |
| 571 | + } |
| 572 | + break; |
| 573 | + |
| 574 | + default: |
| 575 | + $this->warnings[] = 'Unhandled track element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 576 | + break; |
| 577 | + } |
| 578 | + $offset = $subelement_end; |
| 579 | + } |
| 580 | + break; |
| 581 | + default: |
| 582 | + $this->warnings[] = 'Unhandled track element ['.basename(__FILE__).':'.__LINE__.'] ('.$track_entry['id'].'::'.$track_entry['id_name'].') at '.$track_entry['offset']; |
| 583 | + $offset = $track_entry_endoffset; |
| 584 | + break; |
| 585 | + } |
| 586 | + $ThisFileInfo['matroska']['tracks']['tracks'][] = $track_entry; |
| 587 | + } |
| 588 | + break; |
| 589 | + |
| 590 | + case EBML_ID_INFO: // Contains the position of other level 1 elements |
| 591 | + $info_entry = array(); |
| 592 | + while ($offset < $element_end) { |
| 593 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 594 | + $subelement_offset = $offset; |
| 595 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 596 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 597 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 598 | + $subelement_end = $offset + $subelement_length; |
| 599 | + switch ($subelement_id) { |
| 600 | + case EBML_ID_CHAPTERTRANSLATEEDITIONUID: |
| 601 | + case EBML_ID_CHAPTERTRANSLATECODEC: |
| 602 | + case EBML_ID_TIMECODESCALE: |
| 603 | + $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 604 | + break; |
| 605 | + case EBML_ID_DURATION: |
| 606 | + $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 607 | + break; |
| 608 | + case EBML_ID_DATEUTC: |
| 609 | + $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 610 | + $info_entry[$subelement_idname.'_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); |
| 611 | + break; |
| 612 | + case EBML_ID_SEGMENTUID: |
| 613 | + case EBML_ID_PREVUID: |
| 614 | + case EBML_ID_NEXTUID: |
| 615 | + case EBML_ID_SEGMENTFAMILY: |
| 616 | + case EBML_ID_CHAPTERTRANSLATEID: |
| 617 | + $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); |
| 618 | + break; |
| 619 | + case EBML_ID_SEGMENTFILENAME: |
| 620 | + case EBML_ID_PREVFILENAME: |
| 621 | + case EBML_ID_NEXTFILENAME: |
| 622 | + case EBML_ID_TITLE: |
| 623 | + case EBML_ID_MUXINGAPP: |
| 624 | + case EBML_ID_WRITINGAPP: |
| 625 | + $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); |
| 626 | + break; |
| 627 | + default: |
| 628 | + $this->warnings[] = 'Unhandled info element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 629 | + break; |
| 630 | + } |
| 631 | + $offset = $subelement_end; |
| 632 | + } |
| 633 | + $ThisFileInfo['matroska']['info'][] = $info_entry; |
| 634 | + break; |
| 635 | + |
| 636 | + case EBML_ID_CUES: |
| 637 | + $cues_entry = array(); |
| 638 | + while ($offset < $element_end) { |
| 639 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 640 | + $subelement_offset = $offset; |
| 641 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 642 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 643 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 644 | + $subelement_end = $offset + $subelement_length; |
| 645 | + switch ($subelement_id) { |
| 646 | + case EBML_ID_CUEPOINT: |
| 647 | + $cuepoint_entry = array(); |
| 648 | + while ($offset < $subelement_end) { |
| 649 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 650 | + $sub_subelement_offset = $offset; |
| 651 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 652 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 653 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 654 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 655 | + switch ($sub_subelement_id) { |
| 656 | + case EBML_ID_CUETRACKPOSITIONS: |
| 657 | + while ($offset < $sub_subelement_end) { |
| 658 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 659 | + $sub_sub_subelement_offset = $offset; |
| 660 | + $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 661 | + $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 662 | + $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 663 | + $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; |
| 664 | + switch ($sub_sub_subelement_id) { |
| 665 | + case EBML_ID_CUETRACK: |
| 666 | + $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 667 | + break; |
| 668 | + default: |
| 669 | + $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; |
| 670 | + break; |
| 671 | + } |
| 672 | + $offset = $sub_subelement_end; |
| 673 | + } |
| 674 | + break; |
| 675 | + case EBML_ID_CUETIME: |
| 676 | + $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 677 | + break; |
| 678 | + default: |
| 679 | + $this->warnings[] = 'Unhandled cues.cuepoint element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 680 | + break; |
| 681 | + } |
| 682 | + $offset = $sub_subelement_end; |
| 683 | + } |
| 684 | + $cues_entry[] = $cuepoint_entry; |
| 685 | + $offset = $sub_subelement_end; |
| 686 | + break; |
| 687 | + default: |
| 688 | + $this->warnings[] = 'Unhandled cues element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 689 | + break; |
| 690 | + } |
| 691 | + $offset = $subelement_end; |
| 692 | + } |
| 693 | + $ThisFileInfo['matroska']['cues'] = $cues_entry; |
| 694 | + break; |
| 695 | + |
| 696 | + case EBML_ID_TAGS: |
| 697 | + $tags_entry = array(); |
| 698 | + while ($offset < $element_end) { |
| 699 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 700 | + $subelement_offset = $offset; |
| 701 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 702 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 703 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 704 | + $subelement_end = $offset + $subelement_length; |
| 705 | + switch ($subelement_id) { |
| 706 | + case EBML_ID_WRITINGAPP: |
| 707 | + $tags_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); |
| 708 | + break; |
| 709 | + case EBML_ID_TAG: |
| 710 | + $tag_entry = array(); |
| 711 | + while ($offset < $subelement_end) { |
| 712 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 713 | + $sub_subelement_offset = $offset; |
| 714 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 715 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 716 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 717 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 718 | + switch ($sub_subelement_id) { |
| 719 | + case EBML_ID_TARGETS: |
| 720 | + $targets_entry = array(); |
| 721 | + while ($offset < $sub_subelement_end) { |
| 722 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 723 | + $sub_sub_subelement_offset = $offset; |
| 724 | + $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 725 | + $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 726 | + $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 727 | + $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; |
| 728 | + switch ($sub_sub_subelement_id) { |
| 729 | + case EBML_ID_TARGETTYPEVALUE: |
| 730 | + case EBML_ID_EDITIONUID: |
| 731 | + case EBML_ID_CHAPTERUID: |
| 732 | + case EBML_ID_ATTACHMENTUID: |
| 733 | + case EBML_ID_TAGTRACKUID: |
| 734 | + case EBML_ID_TAGCHAPTERUID: |
| 735 | + $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 736 | + break; |
| 737 | + default: |
| 738 | + $this->warnings[] = 'Unhandled tag.targets element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; |
| 739 | + break; |
| 740 | + } |
| 741 | + $offset = $sub_sub_subelement_end; |
| 742 | + } |
| 743 | + $tag_entry[$sub_subelement_idname][] = $targets_entry; |
| 744 | + break; |
| 745 | + case EBML_ID_SIMPLETAG: |
| 746 | + $simpletag_entry = array(); |
| 747 | + while ($offset < $sub_subelement_end) { |
| 748 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 749 | + $sub_sub_subelement_offset = $offset; |
| 750 | + $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 751 | + $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 752 | + $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 753 | + $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; |
| 754 | + switch ($sub_sub_subelement_id) { |
| 755 | + case EBML_ID_TAGNAME: |
| 756 | + case EBML_ID_TAGLANGUAGE: |
| 757 | + case EBML_ID_TAGSTRING: |
| 758 | + case EBML_ID_TAGBINARY: |
| 759 | + $simpletag_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); |
| 760 | + break; |
| 761 | + case EBML_ID_TAGDEFAULT: |
| 762 | + $simpletag_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 763 | + break; |
| 764 | + default: |
| 765 | + $this->warnings[] = 'Unhandled tag.simpletag element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; |
| 766 | + break; |
| 767 | + } |
| 768 | + $offset = $sub_sub_subelement_end; |
| 769 | + } |
| 770 | + $tag_entry[$sub_subelement_idname][] = $simpletag_entry; |
| 771 | + break; |
| 772 | + case EBML_ID_TARGETTYPE: |
| 773 | + $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); |
| 774 | + break; |
| 775 | + case EBML_ID_TRACKUID: |
| 776 | + $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 777 | + break; |
| 778 | + default: |
| 779 | + $this->warnings[] = 'Unhandled tags.tag element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 780 | + break; |
| 781 | + } |
| 782 | + $offset = $sub_subelement_end; |
| 783 | + } |
| 784 | + $tags_entry['tags'][] = $tag_entry; |
| 785 | + $offset = $sub_subelement_end; |
| 786 | + break; |
| 787 | + default: |
| 788 | + $this->warnings[] = 'Unhandled tags element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 789 | + break; |
| 790 | + } |
| 791 | + $offset = $subelement_end; |
| 792 | + } |
| 793 | + $ThisFileInfo['matroska']['tags'] = $tags_entry; |
| 794 | + break; |
| 795 | + |
| 796 | + |
| 797 | + case EBML_ID_ATTACHMENTS: |
| 798 | + while ($offset < $element_end) { |
| 799 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 800 | + $subelement_offset = $offset; |
| 801 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 802 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 803 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 804 | + $subelement_end = $offset + $subelement_length; |
| 805 | + switch ($subelement_id) { |
| 806 | + case EBML_ID_ATTACHEDFILE: |
| 807 | + $attachedfile_entry = array(); |
| 808 | + while ($offset < $subelement_end) { |
| 809 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 810 | + $sub_subelement_offset = $offset; |
| 811 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 812 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 813 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 814 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 815 | + switch ($sub_subelement_id) { |
| 816 | + case EBML_ID_FILEDESCRIPTION: |
| 817 | + case EBML_ID_FILENAME: |
| 818 | + case EBML_ID_FILEMIMETYPE: |
| 819 | + $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); |
| 820 | + break; |
| 821 | + |
| 822 | + case EBML_ID_FILEDATA: |
| 823 | + $attachedfile_entry['data_offset'] = $offset; |
| 824 | + $attachedfile_entry['data_length'] = $sub_subelement_length; |
| 825 | + if ($sub_subelement_length < 1024) { |
| 826 | + $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); |
| 827 | + } |
| 828 | + break; |
| 829 | + |
| 830 | + case EBML_ID_FILEUID: |
| 831 | + $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 832 | + break; |
| 833 | + |
| 834 | + default: |
| 835 | + $this->warnings[] = 'Unhandled attachment.attachedfile element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 836 | + break; |
| 837 | + } |
| 838 | + $offset = $sub_subelement_end; |
| 839 | + } |
| 840 | + $ThisFileInfo['matroska']['attachments'][] = $attachedfile_entry; |
| 841 | + $offset = $sub_subelement_end; |
| 842 | + break; |
| 843 | + default: |
| 844 | + $this->warnings[] = 'Unhandled tags element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 845 | + break; |
| 846 | + } |
| 847 | + $offset = $subelement_end; |
| 848 | + } |
| 849 | + break; |
| 850 | + |
| 851 | + |
| 852 | + case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore |
| 853 | + while ($offset < $element_end) { |
| 854 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 855 | + $subelement_offset = $offset; |
| 856 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 857 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 858 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 859 | + $subelement_end = $offset + $subelement_length; |
| 860 | + switch ($subelement_id) { |
| 861 | + case EBML_ID_EDITIONENTRY: |
| 862 | + $editionentry_entry = array(); |
| 863 | + while ($offset < $subelement_end) { |
| 864 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 865 | + $sub_subelement_offset = $offset; |
| 866 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 867 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 868 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 869 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 870 | + switch ($sub_subelement_id) { |
| 871 | + case EBML_ID_EDITIONUID: |
| 872 | + $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 873 | + break; |
| 874 | + case EBML_ID_EDITIONFLAGHIDDEN: |
| 875 | + case EBML_ID_EDITIONFLAGDEFAULT: |
| 876 | + case EBML_ID_EDITIONFLAGORDERED: |
| 877 | + $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 878 | + break; |
| 879 | + case EBML_ID_CHAPTERATOM: |
| 880 | + $chapteratom_entry = array(); |
| 881 | + while ($offset < $sub_subelement_end) { |
| 882 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 883 | + $sub_sub_subelement_offset = $offset; |
| 884 | + $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 885 | + $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 886 | + $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 887 | + $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; |
| 888 | + switch ($sub_sub_subelement_id) { |
| 889 | + case EBML_ID_CHAPTERSEGMENTUID: |
| 890 | + case EBML_ID_CHAPTERSEGMENTEDITIONUID: |
| 891 | + $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); |
| 892 | + break; |
| 893 | + case EBML_ID_CHAPTERFLAGENABLED: |
| 894 | + case EBML_ID_CHAPTERFLAGHIDDEN: |
| 895 | + $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 896 | + break; |
| 897 | + case EBML_ID_CHAPTERUID: |
| 898 | + case EBML_ID_CHAPTERTIMESTART: |
| 899 | + case EBML_ID_CHAPTERTIMEEND: |
| 900 | + $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); |
| 901 | + break; |
| 902 | + case EBML_ID_CHAPTERTRACK: |
| 903 | + $chaptertrack_entry = array(); |
| 904 | + while ($offset < $sub_sub_subelement_end) { |
| 905 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 906 | + $sub_sub_sub_subelement_offset = $offset; |
| 907 | + $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 908 | + $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); |
| 909 | + $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 910 | + $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; |
| 911 | + switch ($sub_sub_sub_subelement_id) { |
| 912 | + case EBML_ID_CHAPTERTRACKNUMBER: |
| 913 | + $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); |
| 914 | + break; |
| 915 | + default: |
| 916 | + $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; |
| 917 | + break; |
| 918 | + } |
| 919 | + $offset = $sub_sub_sub_subelement_end; |
| 920 | + } |
| 921 | + $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; |
| 922 | + break; |
| 923 | + case EBML_ID_CHAPTERDISPLAY: |
| 924 | + $chapterdisplay_entry = array(); |
| 925 | + while ($offset < $sub_sub_subelement_end) { |
| 926 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 927 | + $sub_sub_sub_subelement_offset = $offset; |
| 928 | + $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 929 | + $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); |
| 930 | + $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 931 | + $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; |
| 932 | + switch ($sub_sub_sub_subelement_id) { |
| 933 | + case EBML_ID_CHAPSTRING: |
| 934 | + case EBML_ID_CHAPLANGUAGE: |
| 935 | + case EBML_ID_CHAPCOUNTRY: |
| 936 | + $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); |
| 937 | + break; |
| 938 | + default: |
| 939 | + $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; |
| 940 | + break; |
| 941 | + } |
| 942 | + $offset = $sub_sub_sub_subelement_end; |
| 943 | + } |
| 944 | + $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; |
| 945 | + break; |
| 946 | + default: |
| 947 | + $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; |
| 948 | + break; |
| 949 | + } |
| 950 | + $offset = $sub_sub_subelement_end; |
| 951 | + } |
| 952 | + $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; |
| 953 | + break; |
| 954 | + default: |
| 955 | + $this->warnings[] = 'Unhandled chapters.editionentry element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 956 | + break; |
| 957 | + } |
| 958 | + $offset = $sub_subelement_end; |
| 959 | + } |
| 960 | + $ThisFileInfo['matroska']['chapters'][] = $editionentry_entry; |
| 961 | + $offset = $sub_subelement_end; |
| 962 | + break; |
| 963 | + default: |
| 964 | + $this->warnings[] = 'Unhandled chapters element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 965 | + break; |
| 966 | + } |
| 967 | + $offset = $subelement_end; |
| 968 | + } |
| 969 | + break; |
| 970 | + |
| 971 | + |
| 972 | + case EBML_ID_VOID: // padding, ignore |
| 973 | + $void_entry = array(); |
| 974 | + $void_entry['offset'] = $offset; |
| 975 | + $ThisFileInfo['matroska']['void'][] = $void_entry; |
| 976 | + break; |
| 977 | + |
| 978 | + case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore |
| 979 | + $cluster_entry = array(); |
| 980 | + while ($offset < $element_end) { |
| 981 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 982 | + $subelement_offset = $offset; |
| 983 | + $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 984 | + $subelement_idname = $this->EBMLidName($subelement_id); |
| 985 | + $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 986 | + $subelement_end = $offset + $subelement_length; |
| 987 | + switch ($subelement_id) { |
| 988 | + case EBML_ID_CLUSTERTIMECODE: |
| 989 | + case EBML_ID_CLUSTERPOSITION: |
| 990 | + case EBML_ID_CLUSTERPREVSIZE: |
| 991 | + $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); |
| 992 | + break; |
| 993 | + |
| 994 | + case EBML_ID_CLUSTERSILENTTRACKS: |
| 995 | + $cluster_silent_tracks = array(); |
| 996 | + while ($offset < $subelement_end) { |
| 997 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 998 | + $sub_subelement_offset = $offset; |
| 999 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1000 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 1001 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1002 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 1003 | + switch ($sub_subelement_id) { |
| 1004 | + case EBML_ID_CLUSTERSILENTTRACKNUMBER: |
| 1005 | + $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 1006 | + break; |
| 1007 | + default: |
| 1008 | + $this->warnings[] = 'Unhandled clusters.silenttracks element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 1009 | + break; |
| 1010 | + } |
| 1011 | + $offset = $sub_subelement_end; |
| 1012 | + } |
| 1013 | + $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; |
| 1014 | + $offset = $sub_subelement_end; |
| 1015 | + break; |
| 1016 | + |
| 1017 | + case EBML_ID_CLUSTERBLOCKGROUP: |
| 1018 | + $cluster_block_group = array('offset'=>$offset); |
| 1019 | + while ($offset < $subelement_end) { |
| 1020 | + $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); |
| 1021 | + $sub_subelement_offset = $offset; |
| 1022 | + $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1023 | + $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); |
| 1024 | + $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1025 | + $sub_subelement_end = $offset + $sub_subelement_length; |
| 1026 | + switch ($sub_subelement_id) { |
| 1027 | + case EBML_ID_CLUSTERBLOCK: |
| 1028 | + $cluster_block_data = array(); |
| 1029 | + $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1030 | + $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); |
| 1031 | + $offset += 2; |
| 1032 | + // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) |
| 1033 | + $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); |
| 1034 | + $offset += 1; |
| 1035 | + //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); |
| 1036 | + $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x08) >> 3); |
| 1037 | + $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); |
| 1038 | + //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); |
| 1039 | + $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); |
| 1040 | + if ($cluster_block_data['flags']['lacing'] != 0) { |
| 1041 | + $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) |
| 1042 | + $offset += 1; |
| 1043 | + if ($cluster_block_data['flags']['lacing'] != 2) { |
| 1044 | + $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). |
| 1045 | + $offset += 1; |
| 1046 | + } |
| 1047 | + } |
| 1048 | + if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { |
| 1049 | + $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; |
| 1050 | + } |
| 1051 | + $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; |
| 1052 | + break; |
| 1053 | + |
| 1054 | + case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int |
| 1055 | + case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int |
| 1056 | + $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); |
| 1057 | + break; |
| 1058 | + |
| 1059 | + case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int |
| 1060 | + $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); |
| 1061 | + break; |
| 1062 | + |
| 1063 | + default: |
| 1064 | + $this->warnings[] = 'Unhandled clusters.blockgroup element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; |
| 1065 | + break; |
| 1066 | + } |
| 1067 | + $offset = $sub_subelement_end; |
| 1068 | + } |
| 1069 | + $cluster_entry[$subelement_idname][] = $cluster_block_group; |
| 1070 | + $offset = $sub_subelement_end; |
| 1071 | + break; |
| 1072 | + |
| 1073 | + case EBML_ID_CLUSTERSIMPLEBLOCK: |
| 1074 | + // http://www.matroska.org/technical/specs/index.html#simpleblock_structure |
| 1075 | + $cluster_block_data = array(); |
| 1076 | + $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); |
| 1077 | + $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); |
| 1078 | + $offset += 2; |
| 1079 | + $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); |
| 1080 | + $offset += 1; |
| 1081 | + $cluster_block_data['flags']['keyframe'] = (($cluster_block_data['flags_raw'] & 0x80) >> 7); |
| 1082 | + $cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0x70) >> 4); |
| 1083 | + $cluster_block_data['flags']['invisible'] = (($cluster_block_data['flags_raw'] & 0x08) >> 3); |
| 1084 | + $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing |
| 1085 | + $cluster_block_data['flags']['discardable'] = (($cluster_block_data['flags_raw'] & 0x01)); |
| 1086 | + |
| 1087 | + if ($cluster_block_data['flags']['lacing'] > 0) { |
| 1088 | + $cluster_block_data['lace_frames'] = 1 + getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); |
| 1089 | + $offset += 1; |
| 1090 | + if ($cluster_block_data['flags']['lacing'] != 0x02) { |
| 1091 | + // *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). |
| 1092 | + $cluster_block_data['lace_frame_size'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); |
| 1093 | + $offset += 1; |
| 1094 | + } |
| 1095 | + } |
| 1096 | + |
| 1097 | + if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { |
| 1098 | + $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; |
| 1099 | + } |
| 1100 | + $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; |
| 1101 | + break; |
| 1102 | + |
| 1103 | + default: |
| 1104 | + $this->warnings[] = 'Unhandled cluster element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; |
| 1105 | + break; |
| 1106 | + } |
| 1107 | + $offset = $subelement_end; |
| 1108 | + } |
| 1109 | + $ThisFileInfo['matroska']['cluster'][] = $cluster_entry; |
| 1110 | + |
| 1111 | + // check to see if all the data we need exists already, if so, break out of the loop |
| 1112 | + if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { |
| 1113 | + if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { |
| 1114 | + break 2; |
| 1115 | + } |
| 1116 | + } |
| 1117 | + break; |
| 1118 | + |
| 1119 | + default: |
| 1120 | + if ($element_data['id_name'] == dechex($element_data['id'])) { |
| 1121 | + $ThisFileInfo['error'][] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].') at '.$element_data_offset; |
| 1122 | + } else { |
| 1123 | + $this->warnings[] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data['offset']; |
| 1124 | + } |
| 1125 | + break; |
| 1126 | + } |
| 1127 | + $offset = $element_end; |
| 1128 | + } |
| 1129 | + break; |
| 1130 | + |
| 1131 | + |
| 1132 | + default: |
| 1133 | + $ThisFileInfo['error'][] = 'Unhandled chunk ['.basename(__FILE__).':'.__LINE__.'] ('.$top_element_id.') at '.$offset; |
| 1134 | + break; |
| 1135 | + } |
| 1136 | + $offset = $top_element_endoffset; |
| 1137 | + } |
| 1138 | + |
| 1139 | + |
| 1140 | + |
| 1141 | + if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { |
| 1142 | + foreach ($ThisFileInfo['matroska']['info'] as $key => $infoarray) { |
| 1143 | + if (isset($infoarray['Duration'])) { |
| 1144 | + // TimecodeScale is how many nanoseconds each Duration unit is |
| 1145 | + $ThisFileInfo['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); |
| 1146 | + break; |
| 1147 | + } |
| 1148 | + } |
| 1149 | + } |
| 1150 | + if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { |
| 1151 | + foreach ($ThisFileInfo['matroska']['tracks']['tracks'] as $key => $trackarray) { |
| 1152 | + $track_info = array(); |
| 1153 | + switch (isset($trackarray['TrackType']) ? $trackarray['TrackType'] : '') { |
| 1154 | + case 1: // Video |
| 1155 | + if (!empty($trackarray['PixelWidth'])) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } |
| 1156 | + if (!empty($trackarray['PixelHeight'])) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } |
| 1157 | + if (!empty($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } |
| 1158 | + if (!empty($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } |
| 1159 | + if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } |
| 1160 | + if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } |
| 1161 | + if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } |
| 1162 | + $ThisFileInfo['video']['streams'][] = $track_info; |
| 1163 | + if (isset($track_info['resolution_x']) && empty($ThisFileInfo['video']['resolution_x'])) { |
| 1164 | + foreach ($track_info as $key => $value) { |
| 1165 | + $ThisFileInfo['video'][$key] = $value; |
| 1166 | + } |
| 1167 | + } |
| 1168 | + break; |
| 1169 | + case 2: // Audio |
| 1170 | + if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } |
| 1171 | + if (!empty($trackarray['SamplingFrequency'])) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } |
| 1172 | + if (!empty($trackarray['Channels'])) { $track_info['channels'] = $trackarray['Channels']; } |
| 1173 | + if (!empty($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } |
| 1174 | + switch (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') { |
| 1175 | + case 'A_PCM/INT/LIT': |
| 1176 | + case 'A_PCM/INT/BIG': |
| 1177 | + $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; |
| 1178 | + break; |
| 1179 | + |
| 1180 | + case 'A_AC3': |
| 1181 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { |
| 1182 | + if (isset($ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { |
| 1183 | + $ac3_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); |
| 1184 | + $getid3_ac3 = new getid3_ac3($fd, $ac3_thisfileinfo); |
| 1185 | + $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $ac3_thisfileinfo; |
| 1186 | + if (!empty($ac3_thisfileinfo['error'])) { |
| 1187 | + foreach ($ac3_thisfileinfo['error'] as $newerror) { |
| 1188 | + $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; |
| 1189 | + } |
| 1190 | + } |
| 1191 | + if (!empty($ac3_thisfileinfo['warning'])) { |
| 1192 | + foreach ($ac3_thisfileinfo['warning'] as $newerror) { |
| 1193 | + $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; |
| 1194 | + } |
| 1195 | + } |
| 1196 | + if (isset($ac3_thisfileinfo['audio']) && is_array($ac3_thisfileinfo['audio'])) { |
| 1197 | + foreach ($ac3_thisfileinfo['audio'] as $key => $value) { |
| 1198 | + $track_info[$key] = $value; |
| 1199 | + } |
| 1200 | + } |
| 1201 | + unset($ac3_thisfileinfo); |
| 1202 | + unset($getid3_ac3); |
| 1203 | + } else { |
| 1204 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $ThisFileInfo[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'; |
| 1205 | + } |
| 1206 | + } else { |
| 1207 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ac3.php"'; |
| 1208 | + } |
| 1209 | + break; |
| 1210 | + |
| 1211 | + case 'A_DTS': |
| 1212 | + $dts_offset = $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]; |
| 1213 | + // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better |
| 1214 | + fseek($fd, $dts_offset, SEEK_SET); |
| 1215 | + $magic_test = fread($fd, 8); |
| 1216 | + for ($i = 0; $i < 4; $i++) { |
| 1217 | + // look to see if DTS "magic" is here, if so adjust offset by that many bytes |
| 1218 | + if (substr($magic_test, $i, 4) == "\x7F\xFE\x80\x01") { |
| 1219 | + $dts_offset += $i; |
| 1220 | + break; |
| 1221 | + } |
| 1222 | + } |
| 1223 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, false)) { |
| 1224 | + $dts_thisfileinfo = array('avdataoffset'=>$dts_offset); |
| 1225 | + $getid3_dts = new getid3_dts($fd, $dts_thisfileinfo); |
| 1226 | + $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $dts_thisfileinfo; |
| 1227 | + if (!empty($dts_thisfileinfo['error'])) { |
| 1228 | + foreach ($dts_thisfileinfo['error'] as $newerror) { |
| 1229 | + $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; |
| 1230 | + } |
| 1231 | + } |
| 1232 | + if (!empty($dts_thisfileinfo['warning'])) { |
| 1233 | + foreach ($dts_thisfileinfo['warning'] as $newerror) { |
| 1234 | + $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; |
| 1235 | + } |
| 1236 | + } |
| 1237 | + if (isset($dts_thisfileinfo['audio']) && is_array($dts_thisfileinfo['audio'])) { |
| 1238 | + foreach ($dts_thisfileinfo['audio'] as $key => $value) { |
| 1239 | + $track_info[$key] = $value; |
| 1240 | + } |
| 1241 | + } |
| 1242 | + unset($dts_thisfileinfo); |
| 1243 | + unset($getid3_dts); |
| 1244 | + } else { |
| 1245 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.dts.php"'; |
| 1246 | + } |
| 1247 | + break; |
| 1248 | + |
| 1249 | + case 'A_AAC': |
| 1250 | +$this->warnings[] = 'This version of getID3() [v'.GETID3_VERSION.'] has problems parsing AAC audio in Matroska containers ['.basename(__FILE__).':'.__LINE__.']'; |
| 1251 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.aac.php', __FILE__, false)) { |
| 1252 | + // $aac_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); |
| 1253 | + // $getid3_aac = new getid3_aac($fd, $aac_thisfileinfo); |
| 1254 | + // $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $aac_thisfileinfo; |
| 1255 | + // if (isset($aac_thisfileinfo['audio']) && is_array($aac_thisfileinfo['audio'])) { |
| 1256 | + // foreach ($aac_thisfileinfo['audio'] as $key => $value) { |
| 1257 | + // $track_info[$key] = $value; |
| 1258 | + // } |
| 1259 | + // } |
| 1260 | + // unset($aac_thisfileinfo); |
| 1261 | + // unset($getid3_aac); |
| 1262 | + } else { |
| 1263 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.aac.php"'; |
| 1264 | + } |
| 1265 | + break; |
| 1266 | + |
| 1267 | + case 'A_MPEG/L3': |
| 1268 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, false)) { |
| 1269 | + $mp3_thisfileinfo = array( |
| 1270 | + 'avdataoffset' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']], |
| 1271 | + 'avdataend' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']] + 1024, |
| 1272 | + ); |
| 1273 | + $getid3_mp3 = new getid3_mp3($fd, $mp3_thisfileinfo); |
| 1274 | + $getid3_mp3->allow_bruteforce = true; |
| 1275 | + //getid3_mp3::getOnlyMPEGaudioInfo($fd, $mp3_thisfileinfo, $offset, false); |
| 1276 | + $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $mp3_thisfileinfo; |
| 1277 | + if (!empty($mp3_thisfileinfo['error'])) { |
| 1278 | + foreach ($mp3_thisfileinfo['error'] as $newerror) { |
| 1279 | + $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; |
| 1280 | + } |
| 1281 | + } |
| 1282 | + if (!empty($mp3_thisfileinfo['warning'])) { |
| 1283 | + foreach ($mp3_thisfileinfo['warning'] as $newerror) { |
| 1284 | + $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; |
| 1285 | + } |
| 1286 | + } |
| 1287 | + if (isset($mp3_thisfileinfo['audio']) && is_array($mp3_thisfileinfo['audio'])) { |
| 1288 | + foreach ($mp3_thisfileinfo['audio'] as $key => $value) { |
| 1289 | + $track_info[$key] = $value; |
| 1290 | + } |
| 1291 | + } |
| 1292 | + unset($mp3_thisfileinfo); |
| 1293 | + unset($getid3_mp3); |
| 1294 | + } else { |
| 1295 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.mp3.php"'; |
| 1296 | + } |
| 1297 | + break; |
| 1298 | + |
| 1299 | + case 'A_VORBIS': |
| 1300 | + if (isset($trackarray['CodecPrivate'])) { |
| 1301 | + // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out |
| 1302 | + $found_vorbis = false; |
| 1303 | + for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { |
| 1304 | + if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { |
| 1305 | + $vorbis_offset--; |
| 1306 | + $found_vorbis = true; |
| 1307 | + break; |
| 1308 | + } |
| 1309 | + } |
| 1310 | + if ($found_vorbis) { |
| 1311 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { |
| 1312 | + $vorbis_fileinfo = array(); |
| 1313 | + $oggpageinfo['page_seqno'] = 0; |
| 1314 | + getid3_ogg::ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $vorbis_fileinfo, $oggpageinfo); |
| 1315 | + $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $vorbis_fileinfo; |
| 1316 | + if (!empty($vorbis_fileinfo['error'])) { |
| 1317 | + foreach ($vorbis_fileinfo['error'] as $newerror) { |
| 1318 | + $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; |
| 1319 | + } |
| 1320 | + } |
| 1321 | + if (!empty($vorbis_fileinfo['warning'])) { |
| 1322 | + foreach ($vorbis_fileinfo['warning'] as $newerror) { |
| 1323 | + $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; |
| 1324 | + } |
| 1325 | + } |
| 1326 | + if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { |
| 1327 | + foreach ($vorbis_fileinfo['audio'] as $key => $value) { |
| 1328 | + $track_info[$key] = $value; |
| 1329 | + } |
| 1330 | + } |
| 1331 | + if (!empty($vorbis_fileinfo['ogg']['bitrate_average'])) { |
| 1332 | + $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; |
| 1333 | + } elseif (!empty($vorbis_fileinfo['ogg']['bitrate_nominal'])) { |
| 1334 | + $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; |
| 1335 | + } |
| 1336 | + unset($vorbis_fileinfo); |
| 1337 | + unset($oggpageinfo); |
| 1338 | + } else { |
| 1339 | + $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'; |
| 1340 | + } |
| 1341 | + } else { |
| 1342 | + } |
| 1343 | + } else { |
| 1344 | + } |
| 1345 | + break; |
| 1346 | + |
| 1347 | + default: |
| 1348 | + $this->warnings[] = 'Unhandled audio type "'.(isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '').'"'; |
| 1349 | + break; |
| 1350 | + } |
| 1351 | + |
| 1352 | + |
| 1353 | + $ThisFileInfo['audio']['streams'][] = $track_info; |
| 1354 | + if (isset($track_info['dataformat']) && empty($ThisFileInfo['audio']['dataformat'])) { |
| 1355 | + foreach ($track_info as $key => $value) { |
| 1356 | + $ThisFileInfo['audio'][$key] = $value; |
| 1357 | + } |
| 1358 | + } |
| 1359 | + break; |
| 1360 | + default: |
| 1361 | + // ignore, do nothing |
| 1362 | + break; |
| 1363 | + } |
| 1364 | + } |
| 1365 | + } |
| 1366 | + |
| 1367 | + if ($this->hide_clusters) { |
| 1368 | + // too much data returned that is usually not useful |
| 1369 | + if (isset($ThisFileInfo['matroska']['segments']) && is_array($ThisFileInfo['matroska']['segments'])) { |
| 1370 | + foreach ($ThisFileInfo['matroska']['segments'] as $key => $segmentsarray) { |
| 1371 | + if ($segmentsarray['id'] == EBML_ID_CLUSTER) { |
| 1372 | + unset($ThisFileInfo['matroska']['segments'][$key]); |
| 1373 | + } |
| 1374 | + } |
| 1375 | + } |
| 1376 | + if (isset($ThisFileInfo['matroska']['seek']) && is_array($ThisFileInfo['matroska']['seek'])) { |
| 1377 | + foreach ($ThisFileInfo['matroska']['seek'] as $key => $seekarray) { |
| 1378 | + if ($seekarray['target_id'] == EBML_ID_CLUSTER) { |
| 1379 | + unset($ThisFileInfo['matroska']['seek'][$key]); |
| 1380 | + } |
| 1381 | + } |
| 1382 | + } |
| 1383 | + //unset($ThisFileInfo['matroska']['cluster']); |
| 1384 | + //unset($ThisFileInfo['matroska']['track_data_offsets']); |
| 1385 | + } |
| 1386 | + |
| 1387 | + if (!empty($ThisFileInfo['video']['streams'])) { |
| 1388 | + $ThisFileInfo['mime_type'] = 'video/x-matroska'; |
| 1389 | + } elseif (!empty($ThisFileInfo['video']['streams'])) { |
| 1390 | + $ThisFileInfo['mime_type'] = 'audio/x-matroska'; |
| 1391 | + } elseif (isset($ThisFileInfo['mime_type'])) { |
| 1392 | + unset($ThisFileInfo['mime_type']); |
| 1393 | + } |
| 1394 | + |
| 1395 | + foreach ($this->warnings as $key => $value) { |
| 1396 | + $ThisFileInfo['warning'][] = $value; |
| 1397 | + } |
| 1398 | + |
| 1399 | + return true; |
| 1400 | + } |
| 1401 | + |
| 1402 | + |
| 1403 | +/////////////////////////////////////// |
| 1404 | + |
| 1405 | + |
| 1406 | + function EnsureBufferHasEnoughData(&$fd, &$EBMLdata, &$offset, &$EBMLdata_offset) { |
| 1407 | + $min_data = 1024; |
| 1408 | + if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) |
| 1409 | + $offset = pow(2,63); |
| 1410 | + return false; |
| 1411 | + } elseif (($offset - $EBMLdata_offset) >= (strlen($EBMLdata) - $min_data)) { |
| 1412 | + fseek($fd, $offset, SEEK_SET); |
| 1413 | + $EBMLdata_offset = ftell($fd); |
| 1414 | + $EBMLdata = fread($fd, $this->read_buffer_size); |
| 1415 | + } |
| 1416 | + return true; |
| 1417 | + } |
| 1418 | + |
| 1419 | + function readEBMLint(&$string, &$offset, $dataoffset=0) { |
| 1420 | + $actual_offset = $offset - $dataoffset; |
| 1421 | + if ($offset > 2147450880) { // 2^31 - 2^15 (2G-32k) |
| 1422 | + $this->warnings[] = 'aborting readEBMLint() because $offset larger than 2GB'; |
| 1423 | + return false; |
| 1424 | + } elseif ($actual_offset >= strlen($string)) { |
| 1425 | + $this->warnings[] = '$actual_offset > $string in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; |
| 1426 | + return false; |
| 1427 | + } elseif ($actual_offset < 0) { |
| 1428 | + $this->warnings[] = '$actual_offset < 0 in readEBMLint($string['.strlen($string).'], '.$offset.', '.$dataoffset.')'; |
| 1429 | + return false; |
| 1430 | + } |
| 1431 | + $first_byte_int = ord($string{$actual_offset}); |
| 1432 | + if (0x80 & $first_byte_int) { |
| 1433 | + $length = 1; |
| 1434 | + } elseif (0x40 & $first_byte_int) { |
| 1435 | + $length = 2; |
| 1436 | + } elseif (0x20 & $first_byte_int) { |
| 1437 | + $length = 3; |
| 1438 | + } elseif (0x10 & $first_byte_int) { |
| 1439 | + $length = 4; |
| 1440 | + } elseif (0x08 & $first_byte_int) { |
| 1441 | + $length = 5; |
| 1442 | + } elseif (0x04 & $first_byte_int) { |
| 1443 | + $length = 6; |
| 1444 | + } elseif (0x02 & $first_byte_int) { |
| 1445 | + $length = 7; |
| 1446 | + } elseif (0x01 & $first_byte_int) { |
| 1447 | + $length = 8; |
| 1448 | + } else { |
| 1449 | + $offset = pow(2,63); // abort processing, skip to end of file |
| 1450 | + $this->warnings[] = 'invalid EBML integer (leading 0x00) at '.$offset; |
| 1451 | + return false; |
| 1452 | + } |
| 1453 | + $int_value = $this->EBML2Int(substr($string, $actual_offset, $length)); |
| 1454 | + $offset += $length; |
| 1455 | + return $int_value; |
| 1456 | + } |
| 1457 | + |
| 1458 | + function EBML2Int($EBMLstring) { |
| 1459 | + // http://matroska.org/specs/ |
| 1460 | + |
| 1461 | + // Element ID coded with an UTF-8 like system: |
| 1462 | + // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) |
| 1463 | + // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) |
| 1464 | + // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) |
| 1465 | + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) |
| 1466 | + // Values with all x at 0 and 1 are reserved (hence the -2). |
| 1467 | + |
| 1468 | + // Data size, in octets, is also coded with an UTF-8 like system : |
| 1469 | + // 1xxx xxxx - value 0 to 2^7-2 |
| 1470 | + // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 |
| 1471 | + // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 |
| 1472 | + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 |
| 1473 | + // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 |
| 1474 | + // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 |
| 1475 | + // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 |
| 1476 | + // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 |
| 1477 | + |
| 1478 | + $first_byte_int = ord($EBMLstring{0}); |
| 1479 | + if (0x80 & $first_byte_int) { |
| 1480 | + $EBMLstring{0} = chr($first_byte_int & 0x7F); |
| 1481 | + } elseif (0x40 & $first_byte_int) { |
| 1482 | + $EBMLstring{0} = chr($first_byte_int & 0x3F); |
| 1483 | + } elseif (0x20 & $first_byte_int) { |
| 1484 | + $EBMLstring{0} = chr($first_byte_int & 0x1F); |
| 1485 | + } elseif (0x10 & $first_byte_int) { |
| 1486 | + $EBMLstring{0} = chr($first_byte_int & 0x0F); |
| 1487 | + } elseif (0x08 & $first_byte_int) { |
| 1488 | + $EBMLstring{0} = chr($first_byte_int & 0x07); |
| 1489 | + } elseif (0x04 & $first_byte_int) { |
| 1490 | + $EBMLstring{0} = chr($first_byte_int & 0x03); |
| 1491 | + } elseif (0x02 & $first_byte_int) { |
| 1492 | + $EBMLstring{0} = chr($first_byte_int & 0x01); |
| 1493 | + } elseif (0x01 & $first_byte_int) { |
| 1494 | + $EBMLstring{0} = chr($first_byte_int & 0x00); |
| 1495 | + } else { |
| 1496 | + return false; |
| 1497 | + } |
| 1498 | + return getid3_lib::BigEndian2Int($EBMLstring); |
| 1499 | + } |
| 1500 | + |
| 1501 | + |
| 1502 | + function EBMLdate2unix($EBMLdatestamp) { |
| 1503 | + // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) |
| 1504 | + // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC |
| 1505 | + return round(($EBMLdatestamp / 1000000000) + 978307200); |
| 1506 | + } |
| 1507 | + |
| 1508 | + |
| 1509 | + function MatroskaBlockLacingType($lacingtype) { |
| 1510 | + // http://matroska.org/technical/specs/index.html#block_structure |
| 1511 | + static $MatroskaBlockLacingType = array(); |
| 1512 | + if (empty($MatroskaBlockLacingType)) { |
| 1513 | + $MatroskaBlockLacingType[0x00] = 'no lacing'; |
| 1514 | + $MatroskaBlockLacingType[0x01] = 'Xiph lacing'; |
| 1515 | + $MatroskaBlockLacingType[0x02] = 'fixed-size lacing'; |
| 1516 | + $MatroskaBlockLacingType[0x03] = 'EBML lacing'; |
| 1517 | + } |
| 1518 | + return (isset($MatroskaBlockLacingType[$lacingtype]) ? $MatroskaBlockLacingType[$lacingtype] : $lacingtype); |
| 1519 | + } |
| 1520 | + |
| 1521 | + function MatroskaCodecIDtoCommonName($codecid) { |
| 1522 | + // http://www.matroska.org/technical/specs/codecid/index.html |
| 1523 | + static $MatroskaCodecIDlist = array(); |
| 1524 | + if (empty($MatroskaCodecIDlist)) { |
| 1525 | + $MatroskaCodecIDlist['A_AAC'] = 'aac'; |
| 1526 | + $MatroskaCodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; |
| 1527 | + $MatroskaCodecIDlist['A_AC3'] = 'ac3'; |
| 1528 | + $MatroskaCodecIDlist['A_DTS'] = 'dts'; |
| 1529 | + $MatroskaCodecIDlist['A_FLAC'] = 'flac'; |
| 1530 | + $MatroskaCodecIDlist['A_MPEG/L1'] = 'mp1'; |
| 1531 | + $MatroskaCodecIDlist['A_MPEG/L2'] = 'mp2'; |
| 1532 | + $MatroskaCodecIDlist['A_MPEG/L3'] = 'mp3'; |
| 1533 | + $MatroskaCodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian |
| 1534 | + $MatroskaCodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian |
| 1535 | + $MatroskaCodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music |
| 1536 | + $MatroskaCodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 |
| 1537 | + $MatroskaCodecIDlist['A_VORBIS'] = 'vorbis'; |
| 1538 | + $MatroskaCodecIDlist['V_MPEG1'] = 'mpeg'; |
| 1539 | + $MatroskaCodecIDlist['V_THEORA'] = 'theora'; |
| 1540 | + $MatroskaCodecIDlist['V_REAL/RV40'] = 'real'; |
| 1541 | + $MatroskaCodecIDlist['V_REAL/RV10'] = 'real'; |
| 1542 | + $MatroskaCodecIDlist['V_REAL/RV20'] = 'real'; |
| 1543 | + $MatroskaCodecIDlist['V_REAL/RV30'] = 'real'; |
| 1544 | + $MatroskaCodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime |
| 1545 | + $MatroskaCodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; |
| 1546 | + $MatroskaCodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; |
| 1547 | + $MatroskaCodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; |
| 1548 | + $MatroskaCodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; |
| 1549 | + } |
| 1550 | + return (isset($MatroskaCodecIDlist[$codecid]) ? $MatroskaCodecIDlist[$codecid] : $codecid); |
| 1551 | + } |
| 1552 | + |
| 1553 | + function EBMLidName($value) { |
| 1554 | + static $EBMLidList = array(); |
| 1555 | + if (empty($EBMLidList)) { |
| 1556 | + $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; |
| 1557 | + $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; |
| 1558 | + $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; |
| 1559 | + $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; |
| 1560 | + $EBMLidList[EBML_ID_ATTACHMENTUID] = 'AttachmentUID'; |
| 1561 | + $EBMLidList[EBML_ID_AUDIO] = 'Audio'; |
| 1562 | + $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; |
| 1563 | + $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; |
| 1564 | + $EBMLidList[EBML_ID_CHANNELS] = 'Channels'; |
| 1565 | + $EBMLidList[EBML_ID_CHAPCOUNTRY] = 'ChapCountry'; |
| 1566 | + $EBMLidList[EBML_ID_CHAPLANGUAGE] = 'ChapLanguage'; |
| 1567 | + $EBMLidList[EBML_ID_CHAPPROCESS] = 'ChapProcess'; |
| 1568 | + $EBMLidList[EBML_ID_CHAPPROCESSCODECID] = 'ChapProcessCodecID'; |
| 1569 | + $EBMLidList[EBML_ID_CHAPPROCESSCOMMAND] = 'ChapProcessCommand'; |
| 1570 | + $EBMLidList[EBML_ID_CHAPPROCESSDATA] = 'ChapProcessData'; |
| 1571 | + $EBMLidList[EBML_ID_CHAPPROCESSPRIVATE] = 'ChapProcessPrivate'; |
| 1572 | + $EBMLidList[EBML_ID_CHAPPROCESSTIME] = 'ChapProcessTime'; |
| 1573 | + $EBMLidList[EBML_ID_CHAPSTRING] = 'ChapString'; |
| 1574 | + $EBMLidList[EBML_ID_CHAPTERATOM] = 'ChapterAtom'; |
| 1575 | + $EBMLidList[EBML_ID_CHAPTERDISPLAY] = 'ChapterDisplay'; |
| 1576 | + $EBMLidList[EBML_ID_CHAPTERFLAGENABLED] = 'ChapterFlagEnabled'; |
| 1577 | + $EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN] = 'ChapterFlagHidden'; |
| 1578 | + $EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV] = 'ChapterPhysicalEquiv'; |
| 1579 | + $EBMLidList[EBML_ID_CHAPTERS] = 'Chapters'; |
| 1580 | + $EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID] = 'ChapterSegmentEditionUID'; |
| 1581 | + $EBMLidList[EBML_ID_CHAPTERSEGMENTUID] = 'ChapterSegmentUID'; |
| 1582 | + $EBMLidList[EBML_ID_CHAPTERTIMEEND] = 'ChapterTimeEnd'; |
| 1583 | + $EBMLidList[EBML_ID_CHAPTERTIMESTART] = 'ChapterTimeStart'; |
| 1584 | + $EBMLidList[EBML_ID_CHAPTERTRACK] = 'ChapterTrack'; |
| 1585 | + $EBMLidList[EBML_ID_CHAPTERTRACKNUMBER] = 'ChapterTrackNumber'; |
| 1586 | + $EBMLidList[EBML_ID_CHAPTERTRANSLATE] = 'ChapterTranslate'; |
| 1587 | + $EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC] = 'ChapterTranslateCodec'; |
| 1588 | + $EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID'; |
| 1589 | + $EBMLidList[EBML_ID_CHAPTERTRANSLATEID] = 'ChapterTranslateID'; |
| 1590 | + $EBMLidList[EBML_ID_CHAPTERUID] = 'ChapterUID'; |
| 1591 | + $EBMLidList[EBML_ID_CLUSTER] = 'Cluster'; |
| 1592 | + $EBMLidList[EBML_ID_CLUSTERBLOCK] = 'ClusterBlock'; |
| 1593 | + $EBMLidList[EBML_ID_CLUSTERBLOCKADDID] = 'ClusterBlockAddID'; |
| 1594 | + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL] = 'ClusterBlockAdditional'; |
| 1595 | + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID] = 'ClusterBlockAdditionID'; |
| 1596 | + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS] = 'ClusterBlockAdditions'; |
| 1597 | + $EBMLidList[EBML_ID_CLUSTERBLOCKDURATION] = 'ClusterBlockDuration'; |
| 1598 | + $EBMLidList[EBML_ID_CLUSTERBLOCKGROUP] = 'ClusterBlockGroup'; |
| 1599 | + $EBMLidList[EBML_ID_CLUSTERBLOCKMORE] = 'ClusterBlockMore'; |
| 1600 | + $EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL] = 'ClusterBlockVirtual'; |
| 1601 | + $EBMLidList[EBML_ID_CLUSTERCODECSTATE] = 'ClusterCodecState'; |
| 1602 | + $EBMLidList[EBML_ID_CLUSTERDELAY] = 'ClusterDelay'; |
| 1603 | + $EBMLidList[EBML_ID_CLUSTERDURATION] = 'ClusterDuration'; |
| 1604 | + $EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK] = 'ClusterEncryptedBlock'; |
| 1605 | + $EBMLidList[EBML_ID_CLUSTERFRAMENUMBER] = 'ClusterFrameNumber'; |
| 1606 | + $EBMLidList[EBML_ID_CLUSTERLACENUMBER] = 'ClusterLaceNumber'; |
| 1607 | + $EBMLidList[EBML_ID_CLUSTERPOSITION] = 'ClusterPosition'; |
| 1608 | + $EBMLidList[EBML_ID_CLUSTERPREVSIZE] = 'ClusterPrevSize'; |
| 1609 | + $EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK] = 'ClusterReferenceBlock'; |
| 1610 | + $EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY] = 'ClusterReferencePriority'; |
| 1611 | + $EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL] = 'ClusterReferenceVirtual'; |
| 1612 | + $EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER] = 'ClusterSilentTrackNumber'; |
| 1613 | + $EBMLidList[EBML_ID_CLUSTERSILENTTRACKS] = 'ClusterSilentTracks'; |
| 1614 | + $EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK] = 'ClusterSimpleBlock'; |
| 1615 | + $EBMLidList[EBML_ID_CLUSTERTIMECODE] = 'ClusterTimecode'; |
| 1616 | + $EBMLidList[EBML_ID_CLUSTERTIMESLICE] = 'ClusterTimeSlice'; |
| 1617 | + $EBMLidList[EBML_ID_CODECDECODEALL] = 'CodecDecodeAll'; |
| 1618 | + $EBMLidList[EBML_ID_CODECDOWNLOADURL] = 'CodecDownloadURL'; |
| 1619 | + $EBMLidList[EBML_ID_CODECID] = 'CodecID'; |
| 1620 | + $EBMLidList[EBML_ID_CODECINFOURL] = 'CodecInfoURL'; |
| 1621 | + $EBMLidList[EBML_ID_CODECNAME] = 'CodecName'; |
| 1622 | + $EBMLidList[EBML_ID_CODECPRIVATE] = 'CodecPrivate'; |
| 1623 | + $EBMLidList[EBML_ID_CODECSETTINGS] = 'CodecSettings'; |
| 1624 | + $EBMLidList[EBML_ID_COLOURSPACE] = 'ColourSpace'; |
| 1625 | + $EBMLidList[EBML_ID_CONTENTCOMPALGO] = 'ContentCompAlgo'; |
| 1626 | + $EBMLidList[EBML_ID_CONTENTCOMPRESSION] = 'ContentCompression'; |
| 1627 | + $EBMLidList[EBML_ID_CONTENTCOMPSETTINGS] = 'ContentCompSettings'; |
| 1628 | + $EBMLidList[EBML_ID_CONTENTENCALGO] = 'ContentEncAlgo'; |
| 1629 | + $EBMLidList[EBML_ID_CONTENTENCKEYID] = 'ContentEncKeyID'; |
| 1630 | + $EBMLidList[EBML_ID_CONTENTENCODING] = 'ContentEncoding'; |
| 1631 | + $EBMLidList[EBML_ID_CONTENTENCODINGORDER] = 'ContentEncodingOrder'; |
| 1632 | + $EBMLidList[EBML_ID_CONTENTENCODINGS] = 'ContentEncodings'; |
| 1633 | + $EBMLidList[EBML_ID_CONTENTENCODINGSCOPE] = 'ContentEncodingScope'; |
| 1634 | + $EBMLidList[EBML_ID_CONTENTENCODINGTYPE] = 'ContentEncodingType'; |
| 1635 | + $EBMLidList[EBML_ID_CONTENTENCRYPTION] = 'ContentEncryption'; |
| 1636 | + $EBMLidList[EBML_ID_CONTENTSIGALGO] = 'ContentSigAlgo'; |
| 1637 | + $EBMLidList[EBML_ID_CONTENTSIGHASHALGO] = 'ContentSigHashAlgo'; |
| 1638 | + $EBMLidList[EBML_ID_CONTENTSIGKEYID] = 'ContentSigKeyID'; |
| 1639 | + $EBMLidList[EBML_ID_CONTENTSIGNATURE] = 'ContentSignature'; |
| 1640 | + $EBMLidList[EBML_ID_CRC32] = 'CRC32'; |
| 1641 | + $EBMLidList[EBML_ID_CUEBLOCKNUMBER] = 'CueBlockNumber'; |
| 1642 | + $EBMLidList[EBML_ID_CUECLUSTERPOSITION] = 'CueClusterPosition'; |
| 1643 | + $EBMLidList[EBML_ID_CUECODECSTATE] = 'CueCodecState'; |
| 1644 | + $EBMLidList[EBML_ID_CUEPOINT] = 'CuePoint'; |
| 1645 | + $EBMLidList[EBML_ID_CUEREFCLUSTER] = 'CueRefCluster'; |
| 1646 | + $EBMLidList[EBML_ID_CUEREFCODECSTATE] = 'CueRefCodecState'; |
| 1647 | + $EBMLidList[EBML_ID_CUEREFERENCE] = 'CueReference'; |
| 1648 | + $EBMLidList[EBML_ID_CUEREFNUMBER] = 'CueRefNumber'; |
| 1649 | + $EBMLidList[EBML_ID_CUEREFTIME] = 'CueRefTime'; |
| 1650 | + $EBMLidList[EBML_ID_CUES] = 'Cues'; |
| 1651 | + $EBMLidList[EBML_ID_CUETIME] = 'CueTime'; |
| 1652 | + $EBMLidList[EBML_ID_CUETRACK] = 'CueTrack'; |
| 1653 | + $EBMLidList[EBML_ID_CUETRACKPOSITIONS] = 'CueTrackPositions'; |
| 1654 | + $EBMLidList[EBML_ID_DATEUTC] = 'DateUTC'; |
| 1655 | + $EBMLidList[EBML_ID_DEFAULTDURATION] = 'DefaultDuration'; |
| 1656 | + $EBMLidList[EBML_ID_DISPLAYHEIGHT] = 'DisplayHeight'; |
| 1657 | + $EBMLidList[EBML_ID_DISPLAYUNIT] = 'DisplayUnit'; |
| 1658 | + $EBMLidList[EBML_ID_DISPLAYWIDTH] = 'DisplayWidth'; |
| 1659 | + $EBMLidList[EBML_ID_DOCTYPE] = 'DocType'; |
| 1660 | + $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; |
| 1661 | + $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; |
| 1662 | + $EBMLidList[EBML_ID_DURATION] = 'Duration'; |
| 1663 | + $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; |
| 1664 | + $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; |
| 1665 | + $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; |
| 1666 | + $EBMLidList[EBML_ID_EBMLVERSION] = 'EBMLVersion'; |
| 1667 | + $EBMLidList[EBML_ID_EDITIONENTRY] = 'EditionEntry'; |
| 1668 | + $EBMLidList[EBML_ID_EDITIONFLAGDEFAULT] = 'EditionFlagDefault'; |
| 1669 | + $EBMLidList[EBML_ID_EDITIONFLAGHIDDEN] = 'EditionFlagHidden'; |
| 1670 | + $EBMLidList[EBML_ID_EDITIONFLAGORDERED] = 'EditionFlagOrdered'; |
| 1671 | + $EBMLidList[EBML_ID_EDITIONUID] = 'EditionUID'; |
| 1672 | + $EBMLidList[EBML_ID_FILEDATA] = 'FileData'; |
| 1673 | + $EBMLidList[EBML_ID_FILEDESCRIPTION] = 'FileDescription'; |
| 1674 | + $EBMLidList[EBML_ID_FILEMIMETYPE] = 'FileMimeType'; |
| 1675 | + $EBMLidList[EBML_ID_FILENAME] = 'FileName'; |
| 1676 | + $EBMLidList[EBML_ID_FILEREFERRAL] = 'FileReferral'; |
| 1677 | + $EBMLidList[EBML_ID_FILEUID] = 'FileUID'; |
| 1678 | + $EBMLidList[EBML_ID_FLAGDEFAULT] = 'FlagDefault'; |
| 1679 | + $EBMLidList[EBML_ID_FLAGENABLED] = 'FlagEnabled'; |
| 1680 | + $EBMLidList[EBML_ID_FLAGFORCED] = 'FlagForced'; |
| 1681 | + $EBMLidList[EBML_ID_FLAGINTERLACED] = 'FlagInterlaced'; |
| 1682 | + $EBMLidList[EBML_ID_FLAGLACING] = 'FlagLacing'; |
| 1683 | + $EBMLidList[EBML_ID_GAMMAVALUE] = 'GammaValue'; |
| 1684 | + $EBMLidList[EBML_ID_INFO] = 'Info'; |
| 1685 | + $EBMLidList[EBML_ID_LANGUAGE] = 'Language'; |
| 1686 | + $EBMLidList[EBML_ID_MAXBLOCKADDITIONID] = 'MaxBlockAdditionID'; |
| 1687 | + $EBMLidList[EBML_ID_MAXCACHE] = 'MaxCache'; |
| 1688 | + $EBMLidList[EBML_ID_MINCACHE] = 'MinCache'; |
| 1689 | + $EBMLidList[EBML_ID_MUXINGAPP] = 'MuxingApp'; |
| 1690 | + $EBMLidList[EBML_ID_NAME] = 'Name'; |
| 1691 | + $EBMLidList[EBML_ID_NEXTFILENAME] = 'NextFilename'; |
| 1692 | + $EBMLidList[EBML_ID_NEXTUID] = 'NextUID'; |
| 1693 | + $EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY] = 'OutputSamplingFrequency'; |
| 1694 | + $EBMLidList[EBML_ID_PIXELCROPBOTTOM] = 'PixelCropBottom'; |
| 1695 | + $EBMLidList[EBML_ID_PIXELCROPLEFT] = 'PixelCropLeft'; |
| 1696 | + $EBMLidList[EBML_ID_PIXELCROPRIGHT] = 'PixelCropRight'; |
| 1697 | + $EBMLidList[EBML_ID_PIXELCROPTOP] = 'PixelCropTop'; |
| 1698 | + $EBMLidList[EBML_ID_PIXELHEIGHT] = 'PixelHeight'; |
| 1699 | + $EBMLidList[EBML_ID_PIXELWIDTH] = 'PixelWidth'; |
| 1700 | + $EBMLidList[EBML_ID_PREVFILENAME] = 'PrevFilename'; |
| 1701 | + $EBMLidList[EBML_ID_PREVUID] = 'PrevUID'; |
| 1702 | + $EBMLidList[EBML_ID_SAMPLINGFREQUENCY] = 'SamplingFrequency'; |
| 1703 | + $EBMLidList[EBML_ID_SEEK] = 'Seek'; |
| 1704 | + $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; |
| 1705 | + $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; |
| 1706 | + $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; |
| 1707 | + $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; |
| 1708 | + $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; |
| 1709 | + $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; |
| 1710 | + $EBMLidList[EBML_ID_SIMPLETAG] = 'SimpleTag'; |
| 1711 | + $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; |
| 1712 | + $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; |
| 1713 | + $EBMLidList[EBML_ID_TAG] = 'Tag'; |
| 1714 | + $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; |
| 1715 | + $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; |
| 1716 | + $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; |
| 1717 | + $EBMLidList[EBML_ID_TAGEDITIONUID] = 'TagEditionUID'; |
| 1718 | + $EBMLidList[EBML_ID_TAGLANGUAGE] = 'TagLanguage'; |
| 1719 | + $EBMLidList[EBML_ID_TAGNAME] = 'TagName'; |
| 1720 | + $EBMLidList[EBML_ID_TAGTRACKUID] = 'TagTrackUID'; |
| 1721 | + $EBMLidList[EBML_ID_TAGS] = 'Tags'; |
| 1722 | + $EBMLidList[EBML_ID_TAGSTRING] = 'TagString'; |
| 1723 | + $EBMLidList[EBML_ID_TARGETS] = 'Targets'; |
| 1724 | + $EBMLidList[EBML_ID_TARGETTYPE] = 'TargetType'; |
| 1725 | + $EBMLidList[EBML_ID_TARGETTYPEVALUE] = 'TargetTypeValue'; |
| 1726 | + $EBMLidList[EBML_ID_TIMECODESCALE] = 'TimecodeScale'; |
| 1727 | + $EBMLidList[EBML_ID_TITLE] = 'Title'; |
| 1728 | + $EBMLidList[EBML_ID_TRACKENTRY] = 'TrackEntry'; |
| 1729 | + $EBMLidList[EBML_ID_TRACKNUMBER] = 'TrackNumber'; |
| 1730 | + $EBMLidList[EBML_ID_TRACKOFFSET] = 'TrackOffset'; |
| 1731 | + $EBMLidList[EBML_ID_TRACKOVERLAY] = 'TrackOverlay'; |
| 1732 | + $EBMLidList[EBML_ID_TRACKS] = 'Tracks'; |
| 1733 | + $EBMLidList[EBML_ID_TRACKTIMECODESCALE] = 'TrackTimecodeScale'; |
| 1734 | + $EBMLidList[EBML_ID_TRACKTRANSLATE] = 'TrackTranslate'; |
| 1735 | + $EBMLidList[EBML_ID_TRACKTRANSLATECODEC] = 'TrackTranslateCodec'; |
| 1736 | + $EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID] = 'TrackTranslateEditionUID'; |
| 1737 | + $EBMLidList[EBML_ID_TRACKTRANSLATETRACKID] = 'TrackTranslateTrackID'; |
| 1738 | + $EBMLidList[EBML_ID_TRACKTYPE] = 'TrackType'; |
| 1739 | + $EBMLidList[EBML_ID_TRACKUID] = 'TrackUID'; |
| 1740 | + $EBMLidList[EBML_ID_VIDEO] = 'Video'; |
| 1741 | + $EBMLidList[EBML_ID_VOID] = 'Void'; |
| 1742 | + $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; |
| 1743 | + } |
| 1744 | + return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); |
| 1745 | + } |
| 1746 | + |
| 1747 | +} |
| 1748 | + |
| 1749 | +?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/module.audio.flac.php |
— | — | @@ -0,0 +1,403 @@ |
| 2 | +<?php |
| 3 | +///////////////////////////////////////////////////////////////// |
| 4 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 5 | +// available at http://getid3.sourceforge.net // |
| 6 | +// or http://www.getid3.org // |
| 7 | +///////////////////////////////////////////////////////////////// |
| 8 | +// See readme.txt for more details // |
| 9 | +///////////////////////////////////////////////////////////////// |
| 10 | +// // |
| 11 | +// module.audio.flac.php // |
| 12 | +// module for analyzing FLAC and OggFLAC audio files // |
| 13 | +// dependencies: module.audio.ogg.php // |
| 14 | +// /// |
| 15 | +///////////////////////////////////////////////////////////////// |
| 16 | + |
| 17 | + |
| 18 | +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); |
| 19 | + |
| 20 | +class getid3_flac |
| 21 | +{ |
| 22 | + |
| 23 | + function getid3_flac(&$fd, &$ThisFileInfo) { |
| 24 | + // http://flac.sourceforge.net/format.html |
| 25 | + |
| 26 | + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); |
| 27 | + $StreamMarker = fread($fd, 4); |
| 28 | + if ($StreamMarker != 'fLaC') { |
| 29 | + $ThisFileInfo['error'][] = 'Expecting "fLaC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$StreamMarker.'"'; |
| 30 | + return false; |
| 31 | + } |
| 32 | + $ThisFileInfo['fileformat'] = 'flac'; |
| 33 | + $ThisFileInfo['audio']['dataformat'] = 'flac'; |
| 34 | + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
| 35 | + $ThisFileInfo['audio']['lossless'] = true; |
| 36 | + |
| 37 | + return getid3_flac::FLACparseMETAdata($fd, $ThisFileInfo); |
| 38 | + } |
| 39 | + |
| 40 | + |
| 41 | + static function FLACparseMETAdata(&$fd, &$ThisFileInfo) { |
| 42 | + |
| 43 | + do { |
| 44 | + $METAdataBlockOffset = ftell($fd); |
| 45 | + $METAdataBlockHeader = fread($fd, 4); |
| 46 | + $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); |
| 47 | + $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F; |
| 48 | + $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); |
| 49 | + $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); |
| 50 | + |
| 51 | + if ($METAdataBlockLength < 0) { |
| 52 | + $ThisFileInfo['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; |
| 53 | + break; |
| 54 | + } |
| 55 | + |
| 56 | + $ThisFileInfo['flac'][$METAdataBlockTypeText]['raw'] = array(); |
| 57 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$ThisFileInfo['flac'][$METAdataBlockTypeText]['raw']; |
| 58 | + |
| 59 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; |
| 60 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; |
| 61 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; |
| 62 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; |
| 63 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; |
| 64 | + ob_start(); |
| 65 | + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = fread($fd, $METAdataBlockLength); |
| 66 | + $errormessage = ob_get_contents(); |
| 67 | + ob_end_clean(); |
| 68 | + $ThisFileInfo['avdataoffset'] = ftell($fd); |
| 69 | + |
| 70 | + switch ($METAdataBlockTypeText) { |
| 71 | + case 'STREAMINFO': // 0x00 |
| 72 | + if (!getid3_flac::FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { |
| 73 | + return false; |
| 74 | + } |
| 75 | + break; |
| 76 | + |
| 77 | + case 'PADDING': // 0x01 |
| 78 | + // ignore |
| 79 | + break; |
| 80 | + |
| 81 | + case 'APPLICATION': // 0x02 |
| 82 | + if (!getid3_flac::FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { |
| 83 | + return false; |
| 84 | + } |
| 85 | + break; |
| 86 | + |
| 87 | + case 'SEEKTABLE': // 0x03 |
| 88 | + if (!getid3_flac::FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { |
| 89 | + return false; |
| 90 | + } |
| 91 | + break; |
| 92 | + |
| 93 | + case 'VORBIS_COMMENT': // 0x04 |
| 94 | + $OldOffset = ftell($fd); |
| 95 | + fseek($fd, 0 - $METAdataBlockLength, SEEK_CUR); |
| 96 | + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); |
| 97 | + fseek($fd, $OldOffset, SEEK_SET); |
| 98 | + break; |
| 99 | + |
| 100 | + case 'CUESHEET': // 0x05 |
| 101 | + if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { |
| 102 | + return false; |
| 103 | + } |
| 104 | + break; |
| 105 | + |
| 106 | + case 'PICTURE': // 0x06 |
| 107 | + if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { |
| 108 | + return false; |
| 109 | + } |
| 110 | + break; |
| 111 | + |
| 112 | + default: |
| 113 | + $ThisFileInfo['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; |
| 114 | + break; |
| 115 | + } |
| 116 | + |
| 117 | + } while ($METAdataLastBlockFlag === false); |
| 118 | + |
| 119 | + |
| 120 | + if (isset($ThisFileInfo['flac']['STREAMINFO'])) { |
| 121 | + $ThisFileInfo['flac']['compressed_audio_bytes'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']; |
| 122 | + $ThisFileInfo['flac']['uncompressed_audio_bytes'] = $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] * $ThisFileInfo['flac']['STREAMINFO']['channels'] * ($ThisFileInfo['flac']['STREAMINFO']['bits_per_sample'] / 8); |
| 123 | + if ($ThisFileInfo['flac']['uncompressed_audio_bytes'] == 0) { |
| 124 | + $ThisFileInfo['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; |
| 125 | + return false; |
| 126 | + } |
| 127 | + $ThisFileInfo['flac']['compression_ratio'] = $ThisFileInfo['flac']['compressed_audio_bytes'] / $ThisFileInfo['flac']['uncompressed_audio_bytes']; |
| 128 | + } |
| 129 | + |
| 130 | + // set md5_data_source - built into flac 0.5+ |
| 131 | + if (isset($ThisFileInfo['flac']['STREAMINFO']['audio_signature'])) { |
| 132 | + |
| 133 | + if ($ThisFileInfo['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { |
| 134 | + |
| 135 | + $ThisFileInfo['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'; |
| 136 | + |
| 137 | + } else { |
| 138 | + |
| 139 | + $ThisFileInfo['md5_data_source'] = ''; |
| 140 | + $md5 = $ThisFileInfo['flac']['STREAMINFO']['audio_signature']; |
| 141 | + for ($i = 0; $i < strlen($md5); $i++) { |
| 142 | + $ThisFileInfo['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); |
| 143 | + } |
| 144 | + if (!preg_match('/^[0-9a-f]{32}$/', $ThisFileInfo['md5_data_source'])) { |
| 145 | + unset($ThisFileInfo['md5_data_source']); |
| 146 | + } |
| 147 | + |
| 148 | + } |
| 149 | + |
| 150 | + } |
| 151 | + |
| 152 | + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample']; |
| 153 | + if ($ThisFileInfo['audio']['bits_per_sample'] == 8) { |
| 154 | + // special case |
| 155 | + // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value |
| 156 | + // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed |
| 157 | + $ThisFileInfo['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; |
| 158 | + } |
| 159 | + if (!empty($ThisFileInfo['ogg']['vendor'])) { |
| 160 | + $ThisFileInfo['audio']['encoder'] = $ThisFileInfo['ogg']['vendor']; |
| 161 | + } |
| 162 | + |
| 163 | + return true; |
| 164 | + } |
| 165 | + |
| 166 | + static function FLACmetaBlockTypeLookup($blocktype) { |
| 167 | + static $FLACmetaBlockTypeLookup = array(); |
| 168 | + if (empty($FLACmetaBlockTypeLookup)) { |
| 169 | + $FLACmetaBlockTypeLookup[0] = 'STREAMINFO'; |
| 170 | + $FLACmetaBlockTypeLookup[1] = 'PADDING'; |
| 171 | + $FLACmetaBlockTypeLookup[2] = 'APPLICATION'; |
| 172 | + $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE'; |
| 173 | + $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT'; |
| 174 | + $FLACmetaBlockTypeLookup[5] = 'CUESHEET'; |
| 175 | + $FLACmetaBlockTypeLookup[6] = 'PICTURE'; |
| 176 | + } |
| 177 | + return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved'); |
| 178 | + } |
| 179 | + |
| 180 | + static function FLACapplicationIDLookup($applicationid) { |
| 181 | + static $FLACapplicationIDLookup = array(); |
| 182 | + if (empty($FLACapplicationIDLookup)) { |
| 183 | + // http://flac.sourceforge.net/id.html |
| 184 | + $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol' |
| 185 | + $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL' |
| 186 | + } |
| 187 | + return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved'); |
| 188 | + } |
| 189 | + |
| 190 | + static function FLACpictureTypeLookup($type_id) { |
| 191 | + static $lookup = array ( |
| 192 | + 0 => 'Other', |
| 193 | + 1 => '32x32 pixels \'file icon\' (PNG only)', |
| 194 | + 2 => 'Other file icon', |
| 195 | + 3 => 'Cover (front)', |
| 196 | + 4 => 'Cover (back)', |
| 197 | + 5 => 'Leaflet page', |
| 198 | + 6 => 'Media (e.g. label side of CD)', |
| 199 | + 7 => 'Lead artist/lead performer/soloist', |
| 200 | + 8 => 'Artist/performer', |
| 201 | + 9 => 'Conductor', |
| 202 | + 10 => 'Band/Orchestra', |
| 203 | + 11 => 'Composer', |
| 204 | + 12 => 'Lyricist/text writer', |
| 205 | + 13 => 'Recording Location', |
| 206 | + 14 => 'During recording', |
| 207 | + 15 => 'During performance', |
| 208 | + 16 => 'Movie/video screen capture', |
| 209 | + 17 => 'A bright coloured fish', |
| 210 | + 18 => 'Illustration', |
| 211 | + 19 => 'Band/artist logotype', |
| 212 | + 20 => 'Publisher/Studio logotype', |
| 213 | + ); |
| 214 | + return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); |
| 215 | + } |
| 216 | + |
| 217 | + static function FLACparseSTREAMINFO($METAdataBlockData, &$ThisFileInfo) { |
| 218 | + $offset = 0; |
| 219 | + $ThisFileInfo['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); |
| 220 | + $offset += 2; |
| 221 | + $ThisFileInfo['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); |
| 222 | + $offset += 2; |
| 223 | + $ThisFileInfo['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); |
| 224 | + $offset += 3; |
| 225 | + $ThisFileInfo['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); |
| 226 | + $offset += 3; |
| 227 | + |
| 228 | + $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8)); |
| 229 | + $ThisFileInfo['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20)); |
| 230 | + $ThisFileInfo['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1; |
| 231 | + $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1; |
| 232 | + $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36)); |
| 233 | + $offset += 8; |
| 234 | + |
| 235 | + $ThisFileInfo['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16); |
| 236 | + $offset += 16; |
| 237 | + |
| 238 | + if (!empty($ThisFileInfo['flac']['STREAMINFO']['sample_rate'])) { |
| 239 | + |
| 240 | + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
| 241 | + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['flac']['STREAMINFO']['sample_rate']; |
| 242 | + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['flac']['STREAMINFO']['channels']; |
| 243 | + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample']; |
| 244 | + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] / $ThisFileInfo['flac']['STREAMINFO']['sample_rate']; |
| 245 | + if ($ThisFileInfo['playtime_seconds'] > 0) { |
| 246 | + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; |
| 247 | + } |
| 248 | + |
| 249 | + } else { |
| 250 | + |
| 251 | + $ThisFileInfo['error'][] = 'Corrupt METAdata block: STREAMINFO'; |
| 252 | + return false; |
| 253 | + |
| 254 | + } |
| 255 | + |
| 256 | + unset($ThisFileInfo['flac']['STREAMINFO']['raw']); |
| 257 | + |
| 258 | + return true; |
| 259 | + } |
| 260 | + |
| 261 | + |
| 262 | + static function FLACparseAPPLICATION($METAdataBlockData, &$ThisFileInfo) { |
| 263 | + $offset = 0; |
| 264 | + $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4)); |
| 265 | + $offset += 4; |
| 266 | + $ThisFileInfo['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID); |
| 267 | + $ThisFileInfo['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset); |
| 268 | + $offset = $METAdataBlockLength; |
| 269 | + |
| 270 | + unset($ThisFileInfo['flac']['APPLICATION']['raw']); |
| 271 | + |
| 272 | + return true; |
| 273 | + } |
| 274 | + |
| 275 | + |
| 276 | + static function FLACparseSEEKTABLE($METAdataBlockData, &$ThisFileInfo) { |
| 277 | + $offset = 0; |
| 278 | + $METAdataBlockLength = strlen($METAdataBlockData); |
| 279 | + $placeholderpattern = str_repeat("\xFF", 8); |
| 280 | + while ($offset < $METAdataBlockLength) { |
| 281 | + $SampleNumberString = substr($METAdataBlockData, $offset, 8); |
| 282 | + $offset += 8; |
| 283 | + if ($SampleNumberString == $placeholderpattern) { |
| 284 | + |
| 285 | + // placeholder point |
| 286 | + getid3_lib::safe_inc($ThisFileInfo['flac']['SEEKTABLE']['placeholders'], 1); |
| 287 | + $offset += 10; |
| 288 | + |
| 289 | + } else { |
| 290 | + |
| 291 | + $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); |
| 292 | + $ThisFileInfo['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); |
| 293 | + $offset += 8; |
| 294 | + $ThisFileInfo['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); |
| 295 | + $offset += 2; |
| 296 | + |
| 297 | + } |
| 298 | + } |
| 299 | + |
| 300 | + unset($ThisFileInfo['flac']['SEEKTABLE']['raw']); |
| 301 | + |
| 302 | + return true; |
| 303 | + } |
| 304 | + |
| 305 | + static function FLACparseCUESHEET($METAdataBlockData, &$ThisFileInfo) { |
| 306 | + $offset = 0; |
| 307 | + $ThisFileInfo['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0"); |
| 308 | + $offset += 128; |
| 309 | + $ThisFileInfo['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); |
| 310 | + $offset += 8; |
| 311 | + $ThisFileInfo['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80); |
| 312 | + $offset += 1; |
| 313 | + |
| 314 | + $offset += 258; // reserved |
| 315 | + |
| 316 | + $ThisFileInfo['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); |
| 317 | + $offset += 1; |
| 318 | + |
| 319 | + for ($track = 0; $track < $ThisFileInfo['flac']['CUESHEET']['number_tracks']; $track++) { |
| 320 | + $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); |
| 321 | + $offset += 8; |
| 322 | + $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); |
| 323 | + $offset += 1; |
| 324 | + |
| 325 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; |
| 326 | + |
| 327 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12); |
| 328 | + $offset += 12; |
| 329 | + |
| 330 | + $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); |
| 331 | + $offset += 1; |
| 332 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); |
| 333 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); |
| 334 | + |
| 335 | + $offset += 13; // reserved |
| 336 | + |
| 337 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); |
| 338 | + $offset += 1; |
| 339 | + |
| 340 | + for ($index = 0; $index < $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { |
| 341 | + $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); |
| 342 | + $offset += 8; |
| 343 | + $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); |
| 344 | + $offset += 1; |
| 345 | + |
| 346 | + $offset += 3; // reserved |
| 347 | + |
| 348 | + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; |
| 349 | + } |
| 350 | + } |
| 351 | + |
| 352 | + unset($ThisFileInfo['flac']['CUESHEET']['raw']); |
| 353 | + |
| 354 | + return true; |
| 355 | + } |
| 356 | + |
| 357 | + |
| 358 | + static function FLACparsePICTURE($meta_data_block_data, &$ThisFileInfo) { |
| 359 | + $picture = &$ThisFileInfo['flac']['PICTURE'][sizeof($ThisFileInfo['flac']['PICTURE']) - 1]; |
| 360 | + |
| 361 | + $offset = 0; |
| 362 | + |
| 363 | + $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 364 | + $picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']); |
| 365 | + $offset += 4; |
| 366 | + |
| 367 | + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 368 | + $offset += 4; |
| 369 | + |
| 370 | + $picture['mime_type'] = substr($meta_data_block_data, $offset, $length); |
| 371 | + $offset += $length; |
| 372 | + |
| 373 | + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 374 | + $offset += 4; |
| 375 | + |
| 376 | + $picture['description'] = substr($meta_data_block_data, $offset, $length); |
| 377 | + $offset += $length; |
| 378 | + |
| 379 | + $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 380 | + $offset += 4; |
| 381 | + |
| 382 | + $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 383 | + $offset += 4; |
| 384 | + |
| 385 | + $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 386 | + $offset += 4; |
| 387 | + |
| 388 | + $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 389 | + $offset += 4; |
| 390 | + |
| 391 | + $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); |
| 392 | + $offset += 4; |
| 393 | + |
| 394 | + $picture['image_data'] = substr($meta_data_block_data, $offset, $length); |
| 395 | + $offset += $length; |
| 396 | + $picture['data_length'] = strlen($picture['image_data']); |
| 397 | + |
| 398 | + unset($ThisFileInfo['flac']['PICTURE']['raw']); |
| 399 | + |
| 400 | + return true; |
| 401 | + } |
| 402 | +} |
| 403 | + |
| 404 | +?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/license.txt |
— | — | @@ -0,0 +1,340 @@ |
| 2 | + GNU GENERAL PUBLIC LICENSE |
| 3 | + Version 2, June 1991 |
| 4 | + |
| 5 | + Copyright (C) 1989, 1991 Free Software Foundation, Inc. |
| 6 | + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 7 | + Everyone is permitted to copy and distribute verbatim copies |
| 8 | + of this license document, but changing it is not allowed. |
| 9 | + |
| 10 | + Preamble |
| 11 | + |
| 12 | + The licenses for most software are designed to take away your |
| 13 | +freedom to share and change it. By contrast, the GNU General Public |
| 14 | +License is intended to guarantee your freedom to share and change free |
| 15 | +software--to make sure the software is free for all its users. This |
| 16 | +General Public License applies to most of the Free Software |
| 17 | +Foundation's software and to any other program whose authors commit to |
| 18 | +using it. (Some other Free Software Foundation software is covered by |
| 19 | +the GNU Library General Public License instead.) You can apply it to |
| 20 | +your programs, too. |
| 21 | + |
| 22 | + When we speak of free software, we are referring to freedom, not |
| 23 | +price. Our General Public Licenses are designed to make sure that you |
| 24 | +have the freedom to distribute copies of free software (and charge for |
| 25 | +this service if you wish), that you receive source code or can get it |
| 26 | +if you want it, that you can change the software or use pieces of it |
| 27 | +in new free programs; and that you know you can do these things. |
| 28 | + |
| 29 | + To protect your rights, we need to make restrictions that forbid |
| 30 | +anyone to deny you these rights or to ask you to surrender the rights. |
| 31 | +These restrictions translate to certain responsibilities for you if you |
| 32 | +distribute copies of the software, or if you modify it. |
| 33 | + |
| 34 | + For example, if you distribute copies of such a program, whether |
| 35 | +gratis or for a fee, you must give the recipients all the rights that |
| 36 | +you have. You must make sure that they, too, receive or can get the |
| 37 | +source code. And you must show them these terms so they know their |
| 38 | +rights. |
| 39 | + |
| 40 | + We protect your rights with two steps: (1) copyright the software, and |
| 41 | +(2) offer you this license which gives you legal permission to copy, |
| 42 | +distribute and/or modify the software. |
| 43 | + |
| 44 | + Also, for each author's protection and ours, we want to make certain |
| 45 | +that everyone understands that there is no warranty for this free |
| 46 | +software. If the software is modified by someone else and passed on, we |
| 47 | +want its recipients to know that what they have is not the original, so |
| 48 | +that any problems introduced by others will not reflect on the original |
| 49 | +authors' reputations. |
| 50 | + |
| 51 | + Finally, any free program is threatened constantly by software |
| 52 | +patents. We wish to avoid the danger that redistributors of a free |
| 53 | +program will individually obtain patent licenses, in effect making the |
| 54 | +program proprietary. To prevent this, we have made it clear that any |
| 55 | +patent must be licensed for everyone's free use or not licensed at all. |
| 56 | + |
| 57 | + The precise terms and conditions for copying, distribution and |
| 58 | +modification follow. |
| 59 | + |
| 60 | + GNU GENERAL PUBLIC LICENSE |
| 61 | + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
| 62 | + |
| 63 | + 0. This License applies to any program or other work which contains |
| 64 | +a notice placed by the copyright holder saying it may be distributed |
| 65 | +under the terms of this General Public License. The "Program", below, |
| 66 | +refers to any such program or work, and a "work based on the Program" |
| 67 | +means either the Program or any derivative work under copyright law: |
| 68 | +that is to say, a work containing the Program or a portion of it, |
| 69 | +either verbatim or with modifications and/or translated into another |
| 70 | +language. (Hereinafter, translation is included without limitation in |
| 71 | +the term "modification".) Each licensee is addressed as "you". |
| 72 | + |
| 73 | +Activities other than copying, distribution and modification are not |
| 74 | +covered by this License; they are outside its scope. The act of |
| 75 | +running the Program is not restricted, and the output from the Program |
| 76 | +is covered only if its contents constitute a work based on the |
| 77 | +Program (independent of having been made by running the Program). |
| 78 | +Whether that is true depends on what the Program does. |
| 79 | + |
| 80 | + 1. You may copy and distribute verbatim copies of the Program's |
| 81 | +source code as you receive it, in any medium, provided that you |
| 82 | +conspicuously and appropriately publish on each copy an appropriate |
| 83 | +copyright notice and disclaimer of warranty; keep intact all the |
| 84 | +notices that refer to this License and to the absence of any warranty; |
| 85 | +and give any other recipients of the Program a copy of this License |
| 86 | +along with the Program. |
| 87 | + |
| 88 | +You may charge a fee for the physical act of transferring a copy, and |
| 89 | +you may at your option offer warranty protection in exchange for a fee. |
| 90 | + |
| 91 | + 2. You may modify your copy or copies of the Program or any portion |
| 92 | +of it, thus forming a work based on the Program, and copy and |
| 93 | +distribute such modifications or work under the terms of Section 1 |
| 94 | +above, provided that you also meet all of these conditions: |
| 95 | + |
| 96 | + a) You must cause the modified files to carry prominent notices |
| 97 | + stating that you changed the files and the date of any change. |
| 98 | + |
| 99 | + b) You must cause any work that you distribute or publish, that in |
| 100 | + whole or in part contains or is derived from the Program or any |
| 101 | + part thereof, to be licensed as a whole at no charge to all third |
| 102 | + parties under the terms of this License. |
| 103 | + |
| 104 | + c) If the modified program normally reads commands interactively |
| 105 | + when run, you must cause it, when started running for such |
| 106 | + interactive use in the most ordinary way, to print or display an |
| 107 | + announcement including an appropriate copyright notice and a |
| 108 | + notice that there is no warranty (or else, saying that you provide |
| 109 | + a warranty) and that users may redistribute the program under |
| 110 | + these conditions, and telling the user how to view a copy of this |
| 111 | + License. (Exception: if the Program itself is interactive but |
| 112 | + does not normally print such an announcement, your work based on |
| 113 | + the Program is not required to print an announcement.) |
| 114 | + |
| 115 | +These requirements apply to the modified work as a whole. If |
| 116 | +identifiable sections of that work are not derived from the Program, |
| 117 | +and can be reasonably considered independent and separate works in |
| 118 | +themselves, then this License, and its terms, do not apply to those |
| 119 | +sections when you distribute them as separate works. But when you |
| 120 | +distribute the same sections as part of a whole which is a work based |
| 121 | +on the Program, the distribution of the whole must be on the terms of |
| 122 | +this License, whose permissions for other licensees extend to the |
| 123 | +entire whole, and thus to each and every part regardless of who wrote it. |
| 124 | + |
| 125 | +Thus, it is not the intent of this section to claim rights or contest |
| 126 | +your rights to work written entirely by you; rather, the intent is to |
| 127 | +exercise the right to control the distribution of derivative or |
| 128 | +collective works based on the Program. |
| 129 | + |
| 130 | +In addition, mere aggregation of another work not based on the Program |
| 131 | +with the Program (or with a work based on the Program) on a volume of |
| 132 | +a storage or distribution medium does not bring the other work under |
| 133 | +the scope of this License. |
| 134 | + |
| 135 | + 3. You may copy and distribute the Program (or a work based on it, |
| 136 | +under Section 2) in object code or executable form under the terms of |
| 137 | +Sections 1 and 2 above provided that you also do one of the following: |
| 138 | + |
| 139 | + a) Accompany it with the complete corresponding machine-readable |
| 140 | + source code, which must be distributed under the terms of Sections |
| 141 | + 1 and 2 above on a medium customarily used for software interchange; or, |
| 142 | + |
| 143 | + b) Accompany it with a written offer, valid for at least three |
| 144 | + years, to give any third party, for a charge no more than your |
| 145 | + cost of physically performing source distribution, a complete |
| 146 | + machine-readable copy of the corresponding source code, to be |
| 147 | + distributed under the terms of Sections 1 and 2 above on a medium |
| 148 | + customarily used for software interchange; or, |
| 149 | + |
| 150 | + c) Accompany it with the information you received as to the offer |
| 151 | + to distribute corresponding source code. (This alternative is |
| 152 | + allowed only for noncommercial distribution and only if you |
| 153 | + received the program in object code or executable form with such |
| 154 | + an offer, in accord with Subsection b above.) |
| 155 | + |
| 156 | +The source code for a work means the preferred form of the work for |
| 157 | +making modifications to it. For an executable work, complete source |
| 158 | +code means all the source code for all modules it contains, plus any |
| 159 | +associated interface definition files, plus the scripts used to |
| 160 | +control compilation and installation of the executable. However, as a |
| 161 | +special exception, the source code distributed need not include |
| 162 | +anything that is normally distributed (in either source or binary |
| 163 | +form) with the major components (compiler, kernel, and so on) of the |
| 164 | +operating system on which the executable runs, unless that component |
| 165 | +itself accompanies the executable. |
| 166 | + |
| 167 | +If distribution of executable or object code is made by offering |
| 168 | +access to copy from a designated place, then offering equivalent |
| 169 | +access to copy the source code from the same place counts as |
| 170 | +distribution of the source code, even though third parties are not |
| 171 | +compelled to copy the source along with the object code. |
| 172 | + |
| 173 | + 4. You may not copy, modify, sublicense, or distribute the Program |
| 174 | +except as expressly provided under this License. Any attempt |
| 175 | +otherwise to copy, modify, sublicense or distribute the Program is |
| 176 | +void, and will automatically terminate your rights under this License. |
| 177 | +However, parties who have received copies, or rights, from you under |
| 178 | +this License will not have their licenses terminated so long as such |
| 179 | +parties remain in full compliance. |
| 180 | + |
| 181 | + 5. You are not required to accept this License, since you have not |
| 182 | +signed it. However, nothing else grants you permission to modify or |
| 183 | +distribute the Program or its derivative works. These actions are |
| 184 | +prohibited by law if you do not accept this License. Therefore, by |
| 185 | +modifying or distributing the Program (or any work based on the |
| 186 | +Program), you indicate your acceptance of this License to do so, and |
| 187 | +all its terms and conditions for copying, distributing or modifying |
| 188 | +the Program or works based on it. |
| 189 | + |
| 190 | + 6. Each time you redistribute the Program (or any work based on the |
| 191 | +Program), the recipient automatically receives a license from the |
| 192 | +original licensor to copy, distribute or modify the Program subject to |
| 193 | +these terms and conditions. You may not impose any further |
| 194 | +restrictions on the recipients' exercise of the rights granted herein. |
| 195 | +You are not responsible for enforcing compliance by third parties to |
| 196 | +this License. |
| 197 | + |
| 198 | + 7. If, as a consequence of a court judgment or allegation of patent |
| 199 | +infringement or for any other reason (not limited to patent issues), |
| 200 | +conditions are imposed on you (whether by court order, agreement or |
| 201 | +otherwise) that contradict the conditions of this License, they do not |
| 202 | +excuse you from the conditions of this License. If you cannot |
| 203 | +distribute so as to satisfy simultaneously your obligations under this |
| 204 | +License and any other pertinent obligations, then as a consequence you |
| 205 | +may not distribute the Program at all. For example, if a patent |
| 206 | +license would not permit royalty-free redistribution of the Program by |
| 207 | +all those who receive copies directly or indirectly through you, then |
| 208 | +the only way you could satisfy both it and this License would be to |
| 209 | +refrain entirely from distribution of the Program. |
| 210 | + |
| 211 | +If any portion of this section is held invalid or unenforceable under |
| 212 | +any particular circumstance, the balance of the section is intended to |
| 213 | +apply and the section as a whole is intended to apply in other |
| 214 | +circumstances. |
| 215 | + |
| 216 | +It is not the purpose of this section to induce you to infringe any |
| 217 | +patents or other property right claims or to contest validity of any |
| 218 | +such claims; this section has the sole purpose of protecting the |
| 219 | +integrity of the free software distribution system, which is |
| 220 | +implemented by public license practices. Many people have made |
| 221 | +generous contributions to the wide range of software distributed |
| 222 | +through that system in reliance on consistent application of that |
| 223 | +system; it is up to the author/donor to decide if he or she is willing |
| 224 | +to distribute software through any other system and a licensee cannot |
| 225 | +impose that choice. |
| 226 | + |
| 227 | +This section is intended to make thoroughly clear what is believed to |
| 228 | +be a consequence of the rest of this License. |
| 229 | + |
| 230 | + 8. If the distribution and/or use of the Program is restricted in |
| 231 | +certain countries either by patents or by copyrighted interfaces, the |
| 232 | +original copyright holder who places the Program under this License |
| 233 | +may add an explicit geographical distribution limitation excluding |
| 234 | +those countries, so that distribution is permitted only in or among |
| 235 | +countries not thus excluded. In such case, this License incorporates |
| 236 | +the limitation as if written in the body of this License. |
| 237 | + |
| 238 | + 9. The Free Software Foundation may publish revised and/or new versions |
| 239 | +of the General Public License from time to time. Such new versions will |
| 240 | +be similar in spirit to the present version, but may differ in detail to |
| 241 | +address new problems or concerns. |
| 242 | + |
| 243 | +Each version is given a distinguishing version number. If the Program |
| 244 | +specifies a version number of this License which applies to it and "any |
| 245 | +later version", you have the option of following the terms and conditions |
| 246 | +either of that version or of any later version published by the Free |
| 247 | +Software Foundation. If the Program does not specify a version number of |
| 248 | +this License, you may choose any version ever published by the Free Software |
| 249 | +Foundation. |
| 250 | + |
| 251 | + 10. If you wish to incorporate parts of the Program into other free |
| 252 | +programs whose distribution conditions are different, write to the author |
| 253 | +to ask for permission. For software which is copyrighted by the Free |
| 254 | +Software Foundation, write to the Free Software Foundation; we sometimes |
| 255 | +make exceptions for this. Our decision will be guided by the two goals |
| 256 | +of preserving the free status of all derivatives of our free software and |
| 257 | +of promoting the sharing and reuse of software generally. |
| 258 | + |
| 259 | + NO WARRANTY |
| 260 | + |
| 261 | + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
| 262 | +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
| 263 | +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
| 264 | +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED |
| 265 | +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 266 | +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS |
| 267 | +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE |
| 268 | +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, |
| 269 | +REPAIR OR CORRECTION. |
| 270 | + |
| 271 | + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
| 272 | +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
| 273 | +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, |
| 274 | +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING |
| 275 | +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED |
| 276 | +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY |
| 277 | +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER |
| 278 | +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE |
| 279 | +POSSIBILITY OF SUCH DAMAGES. |
| 280 | + |
| 281 | + END OF TERMS AND CONDITIONS |
| 282 | + |
| 283 | + How to Apply These Terms to Your New Programs |
| 284 | + |
| 285 | + If you develop a new program, and you want it to be of the greatest |
| 286 | +possible use to the public, the best way to achieve this is to make it |
| 287 | +free software which everyone can redistribute and change under these terms. |
| 288 | + |
| 289 | + To do so, attach the following notices to the program. It is safest |
| 290 | +to attach them to the start of each source file to most effectively |
| 291 | +convey the exclusion of warranty; and each file should have at least |
| 292 | +the "copyright" line and a pointer to where the full notice is found. |
| 293 | + |
| 294 | + <one line to give the program's name and a brief idea of what it does.> |
| 295 | + Copyright (C) <year> <name of author> |
| 296 | + |
| 297 | + This program is free software; you can redistribute it and/or modify |
| 298 | + it under the terms of the GNU General Public License as published by |
| 299 | + the Free Software Foundation; either version 2 of the License, or |
| 300 | + (at your option) any later version. |
| 301 | + |
| 302 | + This program is distributed in the hope that it will be useful, |
| 303 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 304 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 305 | + GNU General Public License for more details. |
| 306 | + |
| 307 | + You should have received a copy of the GNU General Public License |
| 308 | + along with this program; if not, write to the Free Software |
| 309 | + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 310 | + |
| 311 | + |
| 312 | +Also add information on how to contact you by electronic and paper mail. |
| 313 | + |
| 314 | +If the program is interactive, make it output a short notice like this |
| 315 | +when it starts in an interactive mode: |
| 316 | + |
| 317 | + Gnomovision version 69, Copyright (C) year name of author |
| 318 | + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
| 319 | + This is free software, and you are welcome to redistribute it |
| 320 | + under certain conditions; type `show c' for details. |
| 321 | + |
| 322 | +The hypothetical commands `show w' and `show c' should show the appropriate |
| 323 | +parts of the General Public License. Of course, the commands you use may |
| 324 | +be called something other than `show w' and `show c'; they could even be |
| 325 | +mouse-clicks or menu items--whatever suits your program. |
| 326 | + |
| 327 | +You should also get your employer (if you work as a programmer) or your |
| 328 | +school, if any, to sign a "copyright disclaimer" for the program, if |
| 329 | +necessary. Here is a sample; alter the names: |
| 330 | + |
| 331 | + Yoyodyne, Inc., hereby disclaims all copyright interest in the program |
| 332 | + `Gnomovision' (which makes passes at compilers) written by James Hacker. |
| 333 | + |
| 334 | + <signature of Ty Coon>, 1 April 1989 |
| 335 | + Ty Coon, President of Vice |
| 336 | + |
| 337 | +This General Public License does not permit incorporating your program into |
| 338 | +proprietary programs. If your program is a subroutine library, you may |
| 339 | +consider it more useful to permit linking proprietary applications with the |
| 340 | +library. If this is what you want to do, use the GNU Library General |
| 341 | +Public License instead of this License. |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/license.txt |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 342 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/getid3.php |
— | — | @@ -0,0 +1,1501 @@ |
| 2 | +<?php |
| 3 | +///////////////////////////////////////////////////////////////// |
| 4 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 5 | +// available at http://getid3.sourceforge.net // |
| 6 | +// or http://www.getid3.org // |
| 7 | +///////////////////////////////////////////////////////////////// |
| 8 | +// // |
| 9 | +// Please see readme.txt for more information // |
| 10 | +// /// |
| 11 | +///////////////////////////////////////////////////////////////// |
| 12 | + |
| 13 | +// Defines |
| 14 | +define('GETID3_VERSION', '1.8.4-20110203'); |
| 15 | +define('GETID3_FREAD_BUFFER_SIZE', 16384); // read buffer size in bytes |
| 16 | + |
| 17 | + |
| 18 | +// attempt to define temp dir as something flexible but reliable |
| 19 | +$temp_dir = ini_get('upload_tmp_dir'); |
| 20 | +if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { |
| 21 | + $temp_dir = ''; |
| 22 | +} |
| 23 | +if (!$temp_dir && function_exists('sys_get_temp_dir')) { |
| 24 | + // PHP v5.2.1+ |
| 25 | + // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts |
| 26 | + $temp_dir = sys_get_temp_dir(); |
| 27 | +} |
| 28 | +$temp_dir = realpath($temp_dir); |
| 29 | +$open_basedir = ini_get('open_basedir'); |
| 30 | +if ($open_basedir) { |
| 31 | + // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" |
| 32 | + $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); |
| 33 | + $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); |
| 34 | + if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { |
| 35 | + $temp_dir .= DIRECTORY_SEPARATOR; |
| 36 | + } |
| 37 | + $found_valid_tempdir = false; |
| 38 | + $open_basedirs = explode(':', $open_basedir); |
| 39 | + foreach ($open_basedirs as $basedir) { |
| 40 | + if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { |
| 41 | + $basedir .= DIRECTORY_SEPARATOR; |
| 42 | + } |
| 43 | + if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { |
| 44 | + $found_valid_tempdir = true; |
| 45 | + break; |
| 46 | + } |
| 47 | + } |
| 48 | + if (!$found_valid_tempdir) { |
| 49 | + $temp_dir = ''; |
| 50 | + } |
| 51 | + unset($open_basedirs, $found_valid_tempdir, $basedir); |
| 52 | +} |
| 53 | +if (!$temp_dir) { |
| 54 | + $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir |
| 55 | +} |
| 56 | +// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system |
| 57 | +define('GETID3_TEMP_DIR', $temp_dir); |
| 58 | +unset($open_basedir, $temp_dir); |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | +class getID3 |
| 63 | +{ |
| 64 | + // public: Settings |
| 65 | + var $encoding = 'ISO-8859-1'; // CASE SENSITIVE! - i.e. (must be supported by iconv()) |
| 66 | + // Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE |
| 67 | + |
| 68 | + var $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' |
| 69 | + |
| 70 | + var $tempdir = GETID3_TEMP_DIR; |
| 71 | + |
| 72 | + // public: Optional tag checks - disable for speed. |
| 73 | + var $option_tag_id3v1 = true; // Read and process ID3v1 tags |
| 74 | + var $option_tag_id3v2 = true; // Read and process ID3v2 tags |
| 75 | + var $option_tag_lyrics3 = true; // Read and process Lyrics3 tags |
| 76 | + var $option_tag_apetag = true; // Read and process APE tags |
| 77 | + var $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding |
| 78 | + var $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities |
| 79 | + |
| 80 | + // public: Optional tag/comment calucations |
| 81 | + var $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc |
| 82 | + |
| 83 | + // public: Optional calculations |
| 84 | + var $option_md5_data = false; // Get MD5 sum of data part - slow |
| 85 | + var $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG |
| 86 | + var $option_sha1_data = false; // Get SHA1 sum of data part - slow |
| 87 | + var $option_max_2gb_check = true; // Check whether file is larger than 2 Gb and thus not supported by PHP |
| 88 | + |
| 89 | + // private |
| 90 | + var $filename; |
| 91 | + |
| 92 | + |
| 93 | + // public: constructor |
| 94 | + function getID3() |
| 95 | + { |
| 96 | + |
| 97 | + $this->startup_error = ''; |
| 98 | + $this->startup_warning = ''; |
| 99 | + |
| 100 | + // Check for PHP version >= 4.2.0 |
| 101 | + if (PHP_VERSION < '4.2.0') { // version_compare not available before PHP v4.1.0, do not use for initial version check |
| 102 | + $this->startup_error .= 'getID3() requires PHP v4.2.0 or higher - you are running v'.PHP_VERSION; |
| 103 | + } |
| 104 | + if (version_compare(PHP_VERSION, '5.0.0', '<')) { |
| 105 | + $this->startup_warning .= 'getID3() v1.8+ recommends PHP v5.0.0 or higher - you are running v'.PHP_VERSION; |
| 106 | + } |
| 107 | + |
| 108 | + // Check memory |
| 109 | + $memory_limit = ini_get('memory_limit'); |
| 110 | + if (preg_match('#([0-9]+)M#i', $memory_limit, $matches)) { |
| 111 | + // could be stored as "16M" rather than 16777216 for example |
| 112 | + $memory_limit = $matches[1] * 1048576; |
| 113 | + } elseif (preg_match('#([0-9]+)G#i', $memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 |
| 114 | + // could be stored as "2G" rather than 2147483648 for example |
| 115 | + $memory_limit = $matches[1] * 1073741824; |
| 116 | + } |
| 117 | + if ($memory_limit <= 0) { |
| 118 | + // memory limits probably disabled |
| 119 | + } elseif ($memory_limit <= 3145728) { |
| 120 | + $this->startup_error .= 'PHP has less than 3MB available memory and will very likely run out. Increase memory_limit in php.ini'; |
| 121 | + } elseif ($memory_limit <= 12582912) { |
| 122 | + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; |
| 123 | + } |
| 124 | + |
| 125 | + // Check safe_mode off |
| 126 | + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { |
| 127 | + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); |
| 128 | + } |
| 129 | + |
| 130 | + if (intval(ini_get('mbstring.func_overload')) > 0) { |
| 131 | + $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); |
| 132 | + } |
| 133 | + |
| 134 | + // Check timezone config setting |
| 135 | + if (!ini_get('date.timezone')) { |
| 136 | + if (function_exists('date_default_timezone_set')) { |
| 137 | + $this->warning('php.ini should have "date.timezone" set, but it does not. Setting timezone to "America/New_York"'); |
| 138 | + date_default_timezone_set('America/New_York'); |
| 139 | + } else { |
| 140 | + $this->warning('php.ini should have "date.timezone" set, but it does not.'); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + // Check for magic_quotes_runtime |
| 145 | + if (function_exists('get_magic_quotes_runtime')) { |
| 146 | + if (get_magic_quotes_runtime()) { |
| 147 | + return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + // Check for magic_quotes_gpc |
| 152 | + if (function_exists('magic_quotes_gpc')) { |
| 153 | + if (get_magic_quotes_gpc()) { |
| 154 | + return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'); |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + // define a constant rather than looking up every time it is needed |
| 159 | + if (!defined('GETID3_OS_ISWINDOWS')) { |
| 160 | + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { |
| 161 | + define('GETID3_OS_ISWINDOWS', true); |
| 162 | + } else { |
| 163 | + define('GETID3_OS_ISWINDOWS', false); |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + // Get base path of getID3() - ONCE |
| 168 | + if (!defined('GETID3_INCLUDEPATH')) { |
| 169 | + foreach (get_included_files() as $key => $val) { |
| 170 | + if (basename($val) == 'getid3.php') { |
| 171 | + define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); |
| 172 | + break; |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + // Load support library |
| 178 | + if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { |
| 179 | + $this->startup_error .= 'getid3.lib.php is missing or corrupt'; |
| 180 | + } |
| 181 | + |
| 182 | + |
| 183 | + // Needed for Windows only: |
| 184 | + // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC |
| 185 | + // as well as other helper functions such as head, tail, md5sum, etc |
| 186 | + // This path cannot contain spaces, but the below code will attempt to get the |
| 187 | + // 8.3-equivalent path automatically |
| 188 | + // IMPORTANT: This path must include the trailing slash |
| 189 | + if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { |
| 190 | + |
| 191 | + $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path |
| 192 | + |
| 193 | + if (!is_dir($helperappsdir)) { |
| 194 | + $this->startup_error .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; |
| 195 | + } elseif (strpos(realpath($helperappsdir), ' ') !== false) { |
| 196 | + $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); |
| 197 | + $path_so_far = array(); |
| 198 | + foreach ($DirPieces as $key => $value) { |
| 199 | + if (strpos($value, ' ') !== false) { |
| 200 | + if (!empty($path_so_far)) { |
| 201 | + $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); |
| 202 | + $dir_listing = `$commandline`; |
| 203 | + $lines = explode("\n", $dir_listing); |
| 204 | + foreach ($lines as $line) { |
| 205 | + $line = trim($line); |
| 206 | + if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { |
| 207 | + list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; |
| 208 | + if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) { |
| 209 | + $value = $shortname; |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | + } else { |
| 214 | + $this->startup_error .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'; |
| 215 | + } |
| 216 | + } |
| 217 | + $path_so_far[] = $value; |
| 218 | + } |
| 219 | + $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); |
| 220 | + } |
| 221 | + define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); |
| 222 | + } |
| 223 | + |
| 224 | + } |
| 225 | + |
| 226 | + |
| 227 | + // public: setOption |
| 228 | + function setOption($optArray) { |
| 229 | + if (!is_array($optArray) || empty($optArray)) { |
| 230 | + return false; |
| 231 | + } |
| 232 | + foreach ($optArray as $opt => $val) { |
| 233 | + //if (isset($this, $opt) === false) { |
| 234 | + if (isset($this->$opt) === false) { |
| 235 | + continue; |
| 236 | + } |
| 237 | + $this->$opt = $val; |
| 238 | + } |
| 239 | + return true; |
| 240 | + } |
| 241 | + |
| 242 | + |
| 243 | + // public: analyze file - replaces GetAllFileInfo() and GetTagOnly() |
| 244 | + function analyze($filename) { |
| 245 | + if (!empty($this->startup_error)) { |
| 246 | + return $this->error($this->startup_error); |
| 247 | + } |
| 248 | + if (!empty($this->startup_warning)) { |
| 249 | + $this->warning($this->startup_warning); |
| 250 | + } |
| 251 | + |
| 252 | + // init result array and set parameters |
| 253 | + $this->info = array(); |
| 254 | + $this->info['GETID3_VERSION'] = GETID3_VERSION; |
| 255 | + |
| 256 | + // Check encoding/iconv support |
| 257 | + if (!function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { |
| 258 | + $errormessage = 'iconv() support is needed for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; |
| 259 | + if (GETID3_OS_ISWINDOWS) { |
| 260 | + $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; |
| 261 | + } else { |
| 262 | + $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; |
| 263 | + } |
| 264 | + return $this->error($errormessage); |
| 265 | + } |
| 266 | + |
| 267 | + // remote files not supported |
| 268 | + if (preg_match('/^(ht|f)tp:\/\//', $filename)) { |
| 269 | + return $this->error('Remote files are not supported in this version of getID3() - please copy the file locally first'); |
| 270 | + } |
| 271 | + |
| 272 | + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); |
| 273 | + $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); |
| 274 | + |
| 275 | + // open local file |
| 276 | + if (file_exists($filename)) { |
| 277 | + ob_start(); |
| 278 | + if ($fp = fopen($filename, 'rb')) { |
| 279 | + // great |
| 280 | + ob_end_clean(); |
| 281 | + } else { |
| 282 | + $fopen_error = ob_get_contents(); |
| 283 | + ob_end_clean(); |
| 284 | + return $this->error('Could not open file "'.$filename.'" (fopen says: '.$fopen_error.')'); |
| 285 | + } |
| 286 | + } else { |
| 287 | + return $this->error('Could not open file "'.$filename.'" (does not exist)'); |
| 288 | + } |
| 289 | + |
| 290 | + // set parameters |
| 291 | + $this->info['filesize'] = filesize($filename); |
| 292 | + |
| 293 | + // option_max_2gb_check |
| 294 | + if ($this->option_max_2gb_check) { |
| 295 | + // PHP doesn't support integers larger than 31-bit (~2GB) |
| 296 | + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize |
| 297 | + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer |
| 298 | + fseek($fp, 0, SEEK_END); |
| 299 | + if ((($this->info['filesize'] != 0) && (ftell($fp) == 0)) || |
| 300 | + ($this->info['filesize'] < 0) || |
| 301 | + (ftell($fp) < 0)) { |
| 302 | + $real_filesize = false; |
| 303 | + if (GETID3_OS_ISWINDOWS) { |
| 304 | + $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; |
| 305 | + $dir_output = `$commandline`; |
| 306 | + if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) { |
| 307 | + $real_filesize = (float) $matches[1]; |
| 308 | + } |
| 309 | + } else { |
| 310 | + $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); |
| 311 | + $dir_output = `$commandline`; |
| 312 | + if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) { |
| 313 | + $real_filesize = (float) $matches[1]; |
| 314 | + } |
| 315 | + } |
| 316 | + if ($real_filesize === false) { |
| 317 | + unset($this->info['filesize']); |
| 318 | + fclose($fp); |
| 319 | + return $this->error('File is most likely larger than 2GB and is not supported by PHP'); |
| 320 | + } elseif ($real_filesize < pow(2, 31)) { |
| 321 | + unset($this->info['filesize']); |
| 322 | + fclose($fp); |
| 323 | + return $this->error('PHP seems to think the file is larger than 2GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); |
| 324 | + } |
| 325 | + $this->info['filesize'] = $real_filesize; |
| 326 | + $this->error('File is larger than 2GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); |
| 327 | + } |
| 328 | + } |
| 329 | + |
| 330 | + // set more parameters |
| 331 | + $this->info['avdataoffset'] = 0; |
| 332 | + $this->info['avdataend'] = $this->info['filesize']; |
| 333 | + $this->info['fileformat'] = ''; // filled in later |
| 334 | + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used |
| 335 | + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used |
| 336 | + $this->info['tags'] = array(); // filled in later, unset if not used |
| 337 | + $this->info['error'] = array(); // filled in later, unset if not used |
| 338 | + $this->info['warning'] = array(); // filled in later, unset if not used |
| 339 | + $this->info['comments'] = array(); // filled in later, unset if not used |
| 340 | + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired |
| 341 | + |
| 342 | + // set redundant parameters - might be needed in some include file |
| 343 | + $this->info['filename'] = basename($filename); |
| 344 | + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); |
| 345 | + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; |
| 346 | + |
| 347 | + |
| 348 | + // handle ID3v2 tag - done first - already at beginning of file |
| 349 | + // ID3v2 detection (even if not parsing) is always done otherwise fileformat is much harder to detect |
| 350 | + if ($this->option_tag_id3v2) { |
| 351 | + |
| 352 | + $GETID3_ERRORARRAY = &$this->info['warning']; |
| 353 | + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, false)) { |
| 354 | + $tag = new getid3_id3v2($fp, $this->info); |
| 355 | + unset($tag); |
| 356 | + } |
| 357 | + |
| 358 | + } else { |
| 359 | + |
| 360 | + fseek($fp, 0, SEEK_SET); |
| 361 | + $header = fread($fp, 10); |
| 362 | + if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { |
| 363 | + $this->info['id3v2']['header'] = true; |
| 364 | + $this->info['id3v2']['majorversion'] = ord($header{3}); |
| 365 | + $this->info['id3v2']['minorversion'] = ord($header{4}); |
| 366 | + $this->info['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 |
| 367 | + |
| 368 | + $this->info['id3v2']['tag_offset_start'] = 0; |
| 369 | + $this->info['id3v2']['tag_offset_end'] = $this->info['id3v2']['tag_offset_start'] + $this->info['id3v2']['headerlength']; |
| 370 | + $this->info['avdataoffset'] = $this->info['id3v2']['tag_offset_end']; |
| 371 | + } |
| 372 | + |
| 373 | + } |
| 374 | + |
| 375 | + |
| 376 | + // handle ID3v1 tag |
| 377 | + if ($this->option_tag_id3v1) { |
| 378 | + if (!file_exists(GETID3_INCLUDEPATH.'module.tag.id3v1.php') || !include_once(GETID3_INCLUDEPATH.'module.tag.id3v1.php')) { |
| 379 | + return $this->error('module.tag.id3v1.php is missing - you may disable option_tag_id3v1.'); |
| 380 | + } |
| 381 | + $tag = new getid3_id3v1($fp, $this->info); |
| 382 | + unset($tag); |
| 383 | + } |
| 384 | + |
| 385 | + // handle APE tag |
| 386 | + if ($this->option_tag_apetag) { |
| 387 | + if (!file_exists(GETID3_INCLUDEPATH.'module.tag.apetag.php') || !include_once(GETID3_INCLUDEPATH.'module.tag.apetag.php')) { |
| 388 | + return $this->error('module.tag.apetag.php is missing - you may disable option_tag_apetag.'); |
| 389 | + } |
| 390 | + $tag = new getid3_apetag($fp, $this->info); |
| 391 | + unset($tag); |
| 392 | + } |
| 393 | + |
| 394 | + // handle lyrics3 tag |
| 395 | + if ($this->option_tag_lyrics3) { |
| 396 | + if (!file_exists(GETID3_INCLUDEPATH.'module.tag.lyrics3.php') || !include_once(GETID3_INCLUDEPATH.'module.tag.lyrics3.php')) { |
| 397 | + return $this->error('module.tag.lyrics3.php is missing - you may disable option_tag_lyrics3.'); |
| 398 | + } |
| 399 | + $tag = new getid3_lyrics3($fp, $this->info); |
| 400 | + unset($tag); |
| 401 | + } |
| 402 | + |
| 403 | + // read 32 kb file data |
| 404 | + fseek($fp, $this->info['avdataoffset'], SEEK_SET); |
| 405 | + $formattest = fread($fp, 32774); |
| 406 | + |
| 407 | + // determine format |
| 408 | + $determined_format = $this->GetFileFormat($formattest, $filename); |
| 409 | + |
| 410 | + // unable to determine file format |
| 411 | + if (!$determined_format) { |
| 412 | + fclose($fp); |
| 413 | + return $this->error('unable to determine file format'); |
| 414 | + } |
| 415 | + |
| 416 | + // check for illegal ID3 tags |
| 417 | + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { |
| 418 | + if ($determined_format['fail_id3'] === 'ERROR') { |
| 419 | + fclose($fp); |
| 420 | + return $this->error('ID3 tags not allowed on this file type.'); |
| 421 | + } elseif ($determined_format['fail_id3'] === 'WARNING') { |
| 422 | + $this->info['warning'][] = 'ID3 tags not allowed on this file type.'; |
| 423 | + } |
| 424 | + } |
| 425 | + |
| 426 | + // check for illegal APE tags |
| 427 | + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { |
| 428 | + if ($determined_format['fail_ape'] === 'ERROR') { |
| 429 | + fclose($fp); |
| 430 | + return $this->error('APE tags not allowed on this file type.'); |
| 431 | + } elseif ($determined_format['fail_ape'] === 'WARNING') { |
| 432 | + $this->info['warning'][] = 'APE tags not allowed on this file type.'; |
| 433 | + } |
| 434 | + } |
| 435 | + |
| 436 | + // set mime type |
| 437 | + $this->info['mime_type'] = $determined_format['mime_type']; |
| 438 | + |
| 439 | + // supported format signature pattern detected, but module deleted |
| 440 | + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { |
| 441 | + fclose($fp); |
| 442 | + return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); |
| 443 | + } |
| 444 | + |
| 445 | + // module requires iconv support |
| 446 | + if (!function_exists('iconv') && !empty($determined_format['iconv_req'])) { |
| 447 | + return $this->error('iconv support is required for this module ('.$determined_format['include'].').'); |
| 448 | + } |
| 449 | + |
| 450 | + // include module |
| 451 | + include_once(GETID3_INCLUDEPATH.$determined_format['include']); |
| 452 | + |
| 453 | + // instantiate module class |
| 454 | + $class_name = 'getid3_'.$determined_format['module']; |
| 455 | + if (!class_exists($class_name)) { |
| 456 | + return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); |
| 457 | + } |
| 458 | + if (isset($determined_format['option'])) { |
| 459 | + $class = new $class_name($fp, $this->info, $determined_format['option']); |
| 460 | + } else { |
| 461 | + $class = new $class_name($fp, $this->info); |
| 462 | + } |
| 463 | + unset($class); |
| 464 | + |
| 465 | + // close file |
| 466 | + fclose($fp); |
| 467 | + |
| 468 | + // process all tags - copy to 'tags' and convert charsets |
| 469 | + if ($this->option_tags_process) { |
| 470 | + $this->HandleAllTags(); |
| 471 | + } |
| 472 | + |
| 473 | + // perform more calculations |
| 474 | + if ($this->option_extra_info) { |
| 475 | + $this->ChannelsBitratePlaytimeCalculations(); |
| 476 | + $this->CalculateCompressionRatioVideo(); |
| 477 | + $this->CalculateCompressionRatioAudio(); |
| 478 | + $this->CalculateReplayGain(); |
| 479 | + $this->ProcessAudioStreams(); |
| 480 | + } |
| 481 | + |
| 482 | + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags |
| 483 | + if ($this->option_md5_data) { |
| 484 | + // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too |
| 485 | + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { |
| 486 | + $this->getHashdata('md5'); |
| 487 | + } |
| 488 | + } |
| 489 | + |
| 490 | + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags |
| 491 | + if ($this->option_sha1_data) { |
| 492 | + $this->getHashdata('sha1'); |
| 493 | + } |
| 494 | + |
| 495 | + // remove undesired keys |
| 496 | + $this->CleanUp(); |
| 497 | + |
| 498 | + // return info array |
| 499 | + return $this->info; |
| 500 | + } |
| 501 | + |
| 502 | + |
| 503 | + // private: error handling |
| 504 | + function error($message) { |
| 505 | + |
| 506 | + $this->CleanUp(); |
| 507 | + |
| 508 | + $this->info['error'][] = $message; |
| 509 | + return $this->info; |
| 510 | + } |
| 511 | + |
| 512 | + |
| 513 | + // private: warning handling |
| 514 | + function warning($message) { |
| 515 | + $this->info['warning'][] = $message; |
| 516 | + return true; |
| 517 | + } |
| 518 | + |
| 519 | + |
| 520 | + // private: CleanUp |
| 521 | + function CleanUp() { |
| 522 | + |
| 523 | + // remove possible empty keys |
| 524 | + $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); |
| 525 | + foreach ($AVpossibleEmptyKeys as $dummy => $key) { |
| 526 | + if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { |
| 527 | + unset($this->info['audio'][$key]); |
| 528 | + } |
| 529 | + if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { |
| 530 | + unset($this->info['video'][$key]); |
| 531 | + } |
| 532 | + } |
| 533 | + |
| 534 | + // remove empty root keys |
| 535 | + if (!empty($this->info)) { |
| 536 | + foreach ($this->info as $key => $value) { |
| 537 | + if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { |
| 538 | + unset($this->info[$key]); |
| 539 | + } |
| 540 | + } |
| 541 | + } |
| 542 | + |
| 543 | + // remove meaningless entries from unknown-format files |
| 544 | + if (empty($this->info['fileformat'])) { |
| 545 | + if (isset($this->info['avdataoffset'])) { |
| 546 | + unset($this->info['avdataoffset']); |
| 547 | + } |
| 548 | + if (isset($this->info['avdataend'])) { |
| 549 | + unset($this->info['avdataend']); |
| 550 | + } |
| 551 | + } |
| 552 | + } |
| 553 | + |
| 554 | + |
| 555 | + // return array containing information about all supported formats |
| 556 | + function GetFileFormatArray() { |
| 557 | + static $format_info = array(); |
| 558 | + if (empty($format_info)) { |
| 559 | + $format_info = array( |
| 560 | + |
| 561 | + // Audio formats |
| 562 | + |
| 563 | + // AC-3 - audio - Dolby AC-3 / Dolby Digital |
| 564 | + 'ac3' => array( |
| 565 | + 'pattern' => '^\x0B\x77', |
| 566 | + 'group' => 'audio', |
| 567 | + 'module' => 'ac3', |
| 568 | + 'mime_type' => 'audio/ac3', |
| 569 | + ), |
| 570 | + |
| 571 | + // AAC - audio - Advanced Audio Coding (AAC) - ADIF format |
| 572 | + 'adif' => array( |
| 573 | + 'pattern' => '^ADIF', |
| 574 | + 'group' => 'audio', |
| 575 | + 'module' => 'aac', |
| 576 | + 'option' => 'adif', |
| 577 | + 'mime_type' => 'application/octet-stream', |
| 578 | + 'fail_ape' => 'WARNING', |
| 579 | + ), |
| 580 | + |
| 581 | + |
| 582 | + // AA - audio - Audible Audiobook |
| 583 | + 'adts' => array( |
| 584 | + 'pattern' => '^.{4}\x57\x90\x75\x36', |
| 585 | + 'group' => 'audio', |
| 586 | + 'module' => 'aa', |
| 587 | + 'mime_type' => 'audio/audible ', |
| 588 | + ), |
| 589 | + |
| 590 | + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) |
| 591 | + 'adts' => array( |
| 592 | + 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', |
| 593 | + 'group' => 'audio', |
| 594 | + 'module' => 'aac', |
| 595 | + 'option' => 'adts', |
| 596 | + 'mime_type' => 'application/octet-stream', |
| 597 | + 'fail_ape' => 'WARNING', |
| 598 | + ), |
| 599 | + |
| 600 | + |
| 601 | + // AU - audio - NeXT/Sun AUdio (AU) |
| 602 | + 'au' => array( |
| 603 | + 'pattern' => '^\.snd', |
| 604 | + 'group' => 'audio', |
| 605 | + 'module' => 'au', |
| 606 | + 'mime_type' => 'audio/basic', |
| 607 | + ), |
| 608 | + |
| 609 | + // AVR - audio - Audio Visual Research |
| 610 | + 'avr' => array( |
| 611 | + 'pattern' => '^2BIT', |
| 612 | + 'group' => 'audio', |
| 613 | + 'module' => 'avr', |
| 614 | + 'mime_type' => 'application/octet-stream', |
| 615 | + ), |
| 616 | + |
| 617 | + // BONK - audio - Bonk v0.9+ |
| 618 | + 'bonk' => array( |
| 619 | + 'pattern' => '^\x00(BONK|INFO|META| ID3)', |
| 620 | + 'group' => 'audio', |
| 621 | + 'module' => 'bonk', |
| 622 | + 'mime_type' => 'audio/xmms-bonk', |
| 623 | + ), |
| 624 | + |
| 625 | + // DSS - audio - Digital Speech Standard |
| 626 | + 'dss' => array( |
| 627 | + 'pattern' => '^[\x02]dss', |
| 628 | + 'group' => 'audio', |
| 629 | + 'module' => 'dss', |
| 630 | + 'mime_type' => 'application/octet-stream', |
| 631 | + ), |
| 632 | + |
| 633 | + // DTS - audio - Dolby Theatre System |
| 634 | + 'dts' => array( |
| 635 | + 'pattern' => '^\x7F\xFE\x80\x01', |
| 636 | + 'group' => 'audio', |
| 637 | + 'module' => 'dts', |
| 638 | + 'mime_type' => 'audio/dts', |
| 639 | + ), |
| 640 | + |
| 641 | + // FLAC - audio - Free Lossless Audio Codec |
| 642 | + 'flac' => array( |
| 643 | + 'pattern' => '^fLaC', |
| 644 | + 'group' => 'audio', |
| 645 | + 'module' => 'flac', |
| 646 | + 'mime_type' => 'audio/x-flac', |
| 647 | + ), |
| 648 | + |
| 649 | + // LA - audio - Lossless Audio (LA) |
| 650 | + 'la' => array( |
| 651 | + 'pattern' => '^LA0[2-4]', |
| 652 | + 'group' => 'audio', |
| 653 | + 'module' => 'la', |
| 654 | + 'mime_type' => 'application/octet-stream', |
| 655 | + ), |
| 656 | + |
| 657 | + // LPAC - audio - Lossless Predictive Audio Compression (LPAC) |
| 658 | + 'lpac' => array( |
| 659 | + 'pattern' => '^LPAC', |
| 660 | + 'group' => 'audio', |
| 661 | + 'module' => 'lpac', |
| 662 | + 'mime_type' => 'application/octet-stream', |
| 663 | + ), |
| 664 | + |
| 665 | + // MIDI - audio - MIDI (Musical Instrument Digital Interface) |
| 666 | + 'midi' => array( |
| 667 | + 'pattern' => '^MThd', |
| 668 | + 'group' => 'audio', |
| 669 | + 'module' => 'midi', |
| 670 | + 'mime_type' => 'audio/midi', |
| 671 | + ), |
| 672 | + |
| 673 | + // MAC - audio - Monkey's Audio Compressor |
| 674 | + 'mac' => array( |
| 675 | + 'pattern' => '^MAC ', |
| 676 | + 'group' => 'audio', |
| 677 | + 'module' => 'monkey', |
| 678 | + 'mime_type' => 'application/octet-stream', |
| 679 | + ), |
| 680 | + |
| 681 | +// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available |
| 682 | +// // MOD - audio - MODule (assorted sub-formats) |
| 683 | +// 'mod' => array( |
| 684 | +// 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', |
| 685 | +// 'group' => 'audio', |
| 686 | +// 'module' => 'mod', |
| 687 | +// 'option' => 'mod', |
| 688 | +// 'mime_type' => 'audio/mod', |
| 689 | +// ), |
| 690 | + |
| 691 | + // MOD - audio - MODule (Impulse Tracker) |
| 692 | + 'it' => array( |
| 693 | + 'pattern' => '^IMPM', |
| 694 | + 'group' => 'audio', |
| 695 | + 'module' => 'mod', |
| 696 | + 'option' => 'it', |
| 697 | + 'mime_type' => 'audio/it', |
| 698 | + ), |
| 699 | + |
| 700 | + // MOD - audio - MODule (eXtended Module, various sub-formats) |
| 701 | + 'xm' => array( |
| 702 | + 'pattern' => '^Extended Module', |
| 703 | + 'group' => 'audio', |
| 704 | + 'module' => 'mod', |
| 705 | + 'option' => 'xm', |
| 706 | + 'mime_type' => 'audio/xm', |
| 707 | + ), |
| 708 | + |
| 709 | + // MOD - audio - MODule (ScreamTracker) |
| 710 | + 's3m' => array( |
| 711 | + 'pattern' => '^.{44}SCRM', |
| 712 | + 'group' => 'audio', |
| 713 | + 'module' => 'mod', |
| 714 | + 'option' => 's3m', |
| 715 | + 'mime_type' => 'audio/s3m', |
| 716 | + ), |
| 717 | + |
| 718 | + // MPC - audio - Musepack / MPEGplus |
| 719 | + 'mpc' => array( |
| 720 | + 'pattern' => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])', |
| 721 | + 'group' => 'audio', |
| 722 | + 'module' => 'mpc', |
| 723 | + 'mime_type' => 'audio/x-musepack', |
| 724 | + ), |
| 725 | + |
| 726 | + // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) |
| 727 | + 'mp3' => array( |
| 728 | + 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]', |
| 729 | + 'group' => 'audio', |
| 730 | + 'module' => 'mp3', |
| 731 | + 'mime_type' => 'audio/mpeg', |
| 732 | + ), |
| 733 | + |
| 734 | + // OFR - audio - OptimFROG |
| 735 | + 'ofr' => array( |
| 736 | + 'pattern' => '^(\*RIFF|OFR)', |
| 737 | + 'group' => 'audio', |
| 738 | + 'module' => 'optimfrog', |
| 739 | + 'mime_type' => 'application/octet-stream', |
| 740 | + ), |
| 741 | + |
| 742 | + // RKAU - audio - RKive AUdio compressor |
| 743 | + 'rkau' => array( |
| 744 | + 'pattern' => '^RKA', |
| 745 | + 'group' => 'audio', |
| 746 | + 'module' => 'rkau', |
| 747 | + 'mime_type' => 'application/octet-stream', |
| 748 | + ), |
| 749 | + |
| 750 | + // SHN - audio - Shorten |
| 751 | + 'shn' => array( |
| 752 | + 'pattern' => '^ajkg', |
| 753 | + 'group' => 'audio', |
| 754 | + 'module' => 'shorten', |
| 755 | + 'mime_type' => 'audio/xmms-shn', |
| 756 | + 'fail_id3' => 'ERROR', |
| 757 | + 'fail_ape' => 'ERROR', |
| 758 | + ), |
| 759 | + |
| 760 | + // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) |
| 761 | + 'tta' => array( |
| 762 | + 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)' |
| 763 | + 'group' => 'audio', |
| 764 | + 'module' => 'tta', |
| 765 | + 'mime_type' => 'application/octet-stream', |
| 766 | + ), |
| 767 | + |
| 768 | + // VOC - audio - Creative Voice (VOC) |
| 769 | + 'voc' => array( |
| 770 | + 'pattern' => '^Creative Voice File', |
| 771 | + 'group' => 'audio', |
| 772 | + 'module' => 'voc', |
| 773 | + 'mime_type' => 'audio/voc', |
| 774 | + ), |
| 775 | + |
| 776 | + // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) |
| 777 | + 'vqf' => array( |
| 778 | + 'pattern' => '^TWIN', |
| 779 | + 'group' => 'audio', |
| 780 | + 'module' => 'vqf', |
| 781 | + 'mime_type' => 'application/octet-stream', |
| 782 | + ), |
| 783 | + |
| 784 | + // WV - audio - WavPack (v4.0+) |
| 785 | + 'wv' => array( |
| 786 | + 'pattern' => '^wvpk', |
| 787 | + 'group' => 'audio', |
| 788 | + 'module' => 'wavpack', |
| 789 | + 'mime_type' => 'application/octet-stream', |
| 790 | + ), |
| 791 | + |
| 792 | + |
| 793 | + // Audio-Video formats |
| 794 | + |
| 795 | + // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio |
| 796 | + 'asf' => array( |
| 797 | + 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C', |
| 798 | + 'group' => 'audio-video', |
| 799 | + 'module' => 'asf', |
| 800 | + 'mime_type' => 'video/x-ms-asf', |
| 801 | + 'iconv_req' => false, |
| 802 | + ), |
| 803 | + |
| 804 | + // BINK - audio/video - Bink / Smacker |
| 805 | + 'bink' => array( |
| 806 | + 'pattern' => '^(BIK|SMK)', |
| 807 | + 'group' => 'audio-video', |
| 808 | + 'module' => 'bink', |
| 809 | + 'mime_type' => 'application/octet-stream', |
| 810 | + ), |
| 811 | + |
| 812 | + // FLV - audio/video - FLash Video |
| 813 | + 'flv' => array( |
| 814 | + 'pattern' => '^FLV\x01', |
| 815 | + 'group' => 'audio-video', |
| 816 | + 'module' => 'flv', |
| 817 | + 'mime_type' => 'video/x-flv', |
| 818 | + ), |
| 819 | + |
| 820 | + // MKAV - audio/video - Mastroka |
| 821 | + 'matroska' => array( |
| 822 | + 'pattern' => '^\x1A\x45\xDF\xA3', |
| 823 | + 'group' => 'audio-video', |
| 824 | + 'module' => 'matroska', |
| 825 | + 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska |
| 826 | + ), |
| 827 | + |
| 828 | + // MPEG - audio/video - MPEG (Moving Pictures Experts Group) |
| 829 | + 'mpeg' => array( |
| 830 | + 'pattern' => '^\x00\x00\x01(\xBA|\xB3)', |
| 831 | + 'group' => 'audio-video', |
| 832 | + 'module' => 'mpeg', |
| 833 | + 'mime_type' => 'video/mpeg', |
| 834 | + ), |
| 835 | + |
| 836 | + // NSV - audio/video - Nullsoft Streaming Video (NSV) |
| 837 | + 'nsv' => array( |
| 838 | + 'pattern' => '^NSV[sf]', |
| 839 | + 'group' => 'audio-video', |
| 840 | + 'module' => 'nsv', |
| 841 | + 'mime_type' => 'application/octet-stream', |
| 842 | + ), |
| 843 | + |
| 844 | + // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) |
| 845 | + 'ogg' => array( |
| 846 | + 'pattern' => '^OggS', |
| 847 | + 'group' => 'audio', |
| 848 | + 'module' => 'ogg', |
| 849 | + 'mime_type' => 'application/ogg', |
| 850 | + 'fail_id3' => 'WARNING', |
| 851 | + 'fail_ape' => 'WARNING', |
| 852 | + ), |
| 853 | + |
| 854 | + // QT - audio/video - Quicktime |
| 855 | + 'quicktime' => array( |
| 856 | + 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', |
| 857 | + 'group' => 'audio-video', |
| 858 | + 'module' => 'quicktime', |
| 859 | + 'mime_type' => 'video/quicktime', |
| 860 | + ), |
| 861 | + |
| 862 | + // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) |
| 863 | + 'riff' => array( |
| 864 | + 'pattern' => '^(RIFF|SDSS|FORM)', |
| 865 | + 'group' => 'audio-video', |
| 866 | + 'module' => 'riff', |
| 867 | + 'mime_type' => 'audio/x-wave', |
| 868 | + 'fail_ape' => 'WARNING', |
| 869 | + ), |
| 870 | + |
| 871 | + // Real - audio/video - RealAudio, RealVideo |
| 872 | + 'real' => array( |
| 873 | + 'pattern' => '^(\\.RMF|\\.ra)', |
| 874 | + 'group' => 'audio-video', |
| 875 | + 'module' => 'real', |
| 876 | + 'mime_type' => 'audio/x-realaudio', |
| 877 | + ), |
| 878 | + |
| 879 | + // SWF - audio/video - ShockWave Flash |
| 880 | + 'swf' => array( |
| 881 | + 'pattern' => '^(F|C)WS', |
| 882 | + 'group' => 'audio-video', |
| 883 | + 'module' => 'swf', |
| 884 | + 'mime_type' => 'application/x-shockwave-flash', |
| 885 | + ), |
| 886 | + |
| 887 | + |
| 888 | + // Still-Image formats |
| 889 | + |
| 890 | + // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) |
| 891 | + 'bmp' => array( |
| 892 | + 'pattern' => '^BM', |
| 893 | + 'group' => 'graphic', |
| 894 | + 'module' => 'bmp', |
| 895 | + 'mime_type' => 'image/bmp', |
| 896 | + 'fail_id3' => 'ERROR', |
| 897 | + 'fail_ape' => 'ERROR', |
| 898 | + ), |
| 899 | + |
| 900 | + // GIF - still image - Graphics Interchange Format |
| 901 | + 'gif' => array( |
| 902 | + 'pattern' => '^GIF', |
| 903 | + 'group' => 'graphic', |
| 904 | + 'module' => 'gif', |
| 905 | + 'mime_type' => 'image/gif', |
| 906 | + 'fail_id3' => 'ERROR', |
| 907 | + 'fail_ape' => 'ERROR', |
| 908 | + ), |
| 909 | + |
| 910 | + // JPEG - still image - Joint Photographic Experts Group (JPEG) |
| 911 | + 'jpg' => array( |
| 912 | + 'pattern' => '^\xFF\xD8\xFF', |
| 913 | + 'group' => 'graphic', |
| 914 | + 'module' => 'jpg', |
| 915 | + 'mime_type' => 'image/jpeg', |
| 916 | + 'fail_id3' => 'ERROR', |
| 917 | + 'fail_ape' => 'ERROR', |
| 918 | + ), |
| 919 | + |
| 920 | + // PCD - still image - Kodak Photo CD |
| 921 | + 'pcd' => array( |
| 922 | + 'pattern' => '^.{2048}PCD_IPI\x00', |
| 923 | + 'group' => 'graphic', |
| 924 | + 'module' => 'pcd', |
| 925 | + 'mime_type' => 'image/x-photo-cd', |
| 926 | + 'fail_id3' => 'ERROR', |
| 927 | + 'fail_ape' => 'ERROR', |
| 928 | + ), |
| 929 | + |
| 930 | + |
| 931 | + // PNG - still image - Portable Network Graphics (PNG) |
| 932 | + 'png' => array( |
| 933 | + 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A', |
| 934 | + 'group' => 'graphic', |
| 935 | + 'module' => 'png', |
| 936 | + 'mime_type' => 'image/png', |
| 937 | + 'fail_id3' => 'ERROR', |
| 938 | + 'fail_ape' => 'ERROR', |
| 939 | + ), |
| 940 | + |
| 941 | + |
| 942 | + // SVG - still image - Scalable Vector Graphics (SVG) |
| 943 | + 'svg' => array( |
| 944 | + 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")', |
| 945 | + 'group' => 'graphic', |
| 946 | + 'module' => 'svg', |
| 947 | + 'mime_type' => 'image/svg+xml', |
| 948 | + 'fail_id3' => 'ERROR', |
| 949 | + 'fail_ape' => 'ERROR', |
| 950 | + ), |
| 951 | + |
| 952 | + |
| 953 | + // TIFF - still image - Tagged Information File Format (TIFF) |
| 954 | + 'tiff' => array( |
| 955 | + 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', |
| 956 | + 'group' => 'graphic', |
| 957 | + 'module' => 'tiff', |
| 958 | + 'mime_type' => 'image/tiff', |
| 959 | + 'fail_id3' => 'ERROR', |
| 960 | + 'fail_ape' => 'ERROR', |
| 961 | + ), |
| 962 | + |
| 963 | + |
| 964 | + // Data formats |
| 965 | + |
| 966 | + // ISO - data - International Standards Organization (ISO) CD-ROM Image |
| 967 | + 'iso' => array( |
| 968 | + 'pattern' => '^.{32769}CD001', |
| 969 | + 'group' => 'misc', |
| 970 | + 'module' => 'iso', |
| 971 | + 'mime_type' => 'application/octet-stream', |
| 972 | + 'fail_id3' => 'ERROR', |
| 973 | + 'fail_ape' => 'ERROR', |
| 974 | + 'iconv_req' => false, |
| 975 | + ), |
| 976 | + |
| 977 | + // RAR - data - RAR compressed data |
| 978 | + 'rar' => array( |
| 979 | + 'pattern' => '^Rar\!', |
| 980 | + 'group' => 'archive', |
| 981 | + 'module' => 'rar', |
| 982 | + 'mime_type' => 'application/octet-stream', |
| 983 | + 'fail_id3' => 'ERROR', |
| 984 | + 'fail_ape' => 'ERROR', |
| 985 | + ), |
| 986 | + |
| 987 | + // SZIP - audio/data - SZIP compressed data |
| 988 | + 'szip' => array( |
| 989 | + 'pattern' => '^SZ\x0A\x04', |
| 990 | + 'group' => 'archive', |
| 991 | + 'module' => 'szip', |
| 992 | + 'mime_type' => 'application/octet-stream', |
| 993 | + 'fail_id3' => 'ERROR', |
| 994 | + 'fail_ape' => 'ERROR', |
| 995 | + ), |
| 996 | + |
| 997 | + // TAR - data - TAR compressed data |
| 998 | + 'tar' => array( |
| 999 | + 'pattern' => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}', |
| 1000 | + 'group' => 'archive', |
| 1001 | + 'module' => 'tar', |
| 1002 | + 'mime_type' => 'application/x-tar', |
| 1003 | + 'fail_id3' => 'ERROR', |
| 1004 | + 'fail_ape' => 'ERROR', |
| 1005 | + ), |
| 1006 | + |
| 1007 | + // GZIP - data - GZIP compressed data |
| 1008 | + 'gz' => array( |
| 1009 | + 'pattern' => '^\x1F\x8B\x08', |
| 1010 | + 'group' => 'archive', |
| 1011 | + 'module' => 'gzip', |
| 1012 | + 'mime_type' => 'application/x-gzip', |
| 1013 | + 'fail_id3' => 'ERROR', |
| 1014 | + 'fail_ape' => 'ERROR', |
| 1015 | + ), |
| 1016 | + |
| 1017 | + // ZIP - data - ZIP compressed data |
| 1018 | + 'zip' => array( |
| 1019 | + 'pattern' => '^PK\x03\x04', |
| 1020 | + 'group' => 'archive', |
| 1021 | + 'module' => 'zip', |
| 1022 | + 'mime_type' => 'application/zip', |
| 1023 | + 'fail_id3' => 'ERROR', |
| 1024 | + 'fail_ape' => 'ERROR', |
| 1025 | + ), |
| 1026 | + |
| 1027 | + |
| 1028 | + // Misc other formats |
| 1029 | + |
| 1030 | + // PAR2 - data - Parity Volume Set Specification 2.0 |
| 1031 | + 'par2' => array ( |
| 1032 | + 'pattern' => '^PAR2\x00PKT', |
| 1033 | + 'group' => 'misc', |
| 1034 | + 'module' => 'par2', |
| 1035 | + 'mime_type' => 'application/octet-stream', |
| 1036 | + 'fail_id3' => 'ERROR', |
| 1037 | + 'fail_ape' => 'ERROR', |
| 1038 | + ), |
| 1039 | + |
| 1040 | + // PDF - data - Portable Document Format |
| 1041 | + 'pdf' => array( |
| 1042 | + 'pattern' => '^\x25PDF', |
| 1043 | + 'group' => 'misc', |
| 1044 | + 'module' => 'pdf', |
| 1045 | + 'mime_type' => 'application/pdf', |
| 1046 | + 'fail_id3' => 'ERROR', |
| 1047 | + 'fail_ape' => 'ERROR', |
| 1048 | + ), |
| 1049 | + |
| 1050 | + // MSOFFICE - data - ZIP compressed data |
| 1051 | + 'msoffice' => array( |
| 1052 | + 'pattern' => '^\xD0\xCF\x11\xE0', // D0CF11E == DOCFILE == Microsoft Office Document |
| 1053 | + 'group' => 'misc', |
| 1054 | + 'module' => 'msoffice', |
| 1055 | + 'mime_type' => 'application/octet-stream', |
| 1056 | + 'fail_id3' => 'ERROR', |
| 1057 | + 'fail_ape' => 'ERROR', |
| 1058 | + ), |
| 1059 | + |
| 1060 | + // CUE - data - CUEsheet (index to single-file disc images) |
| 1061 | + 'cue' => array( |
| 1062 | + 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents |
| 1063 | + 'group' => 'misc', |
| 1064 | + 'module' => 'cue', |
| 1065 | + 'mime_type' => 'application/octet-stream', |
| 1066 | + ), |
| 1067 | + |
| 1068 | + ); |
| 1069 | + } |
| 1070 | + |
| 1071 | + return $format_info; |
| 1072 | + } |
| 1073 | + |
| 1074 | + |
| 1075 | + |
| 1076 | + function GetFileFormat(&$filedata, $filename='') { |
| 1077 | + // this function will determine the format of a file based on usually |
| 1078 | + // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, |
| 1079 | + // and in the case of ISO CD image, 6 bytes offset 32kb from the start |
| 1080 | + // of the file). |
| 1081 | + |
| 1082 | + // Identify file format - loop through $format_info and detect with reg expr |
| 1083 | + foreach ($this->GetFileFormatArray() as $format_name => $info) { |
| 1084 | + // The /s switch on preg_match() forces preg_match() NOT to treat |
| 1085 | + // newline (0x0A) characters as special chars but do a binary match |
| 1086 | + if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { |
| 1087 | + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; |
| 1088 | + return $info; |
| 1089 | + } |
| 1090 | + } |
| 1091 | + |
| 1092 | + |
| 1093 | + if (preg_match('#\.mp[123a]$#i', $filename)) { |
| 1094 | + // Too many mp3 encoders on the market put gabage in front of mpeg files |
| 1095 | + // use assume format on these if format detection failed |
| 1096 | + $GetFileFormatArray = $this->GetFileFormatArray(); |
| 1097 | + $info = $GetFileFormatArray['mp3']; |
| 1098 | + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; |
| 1099 | + return $info; |
| 1100 | + } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { |
| 1101 | + // there's not really a useful consistent "magic" at the beginning of .cue files to identify them |
| 1102 | + // so until I think of something better, just go by filename if all other format checks fail |
| 1103 | + // and verify there's at least one instance of "TRACK xx AUDIO" in the file |
| 1104 | + $GetFileFormatArray = $this->GetFileFormatArray(); |
| 1105 | + $info = $GetFileFormatArray['cue']; |
| 1106 | + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; |
| 1107 | + return $info; |
| 1108 | + } |
| 1109 | + |
| 1110 | + return false; |
| 1111 | + } |
| 1112 | + |
| 1113 | + |
| 1114 | + // converts array to $encoding charset from $this->encoding |
| 1115 | + function CharConvert(&$array, $encoding) { |
| 1116 | + |
| 1117 | + // identical encoding - end here |
| 1118 | + if ($encoding == $this->encoding) { |
| 1119 | + return; |
| 1120 | + } |
| 1121 | + |
| 1122 | + // loop thru array |
| 1123 | + foreach ($array as $key => $value) { |
| 1124 | + |
| 1125 | + // go recursive |
| 1126 | + if (is_array($value)) { |
| 1127 | + $this->CharConvert($array[$key], $encoding); |
| 1128 | + } |
| 1129 | + |
| 1130 | + // convert string |
| 1131 | + elseif (is_string($value)) { |
| 1132 | + $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); |
| 1133 | + } |
| 1134 | + } |
| 1135 | + } |
| 1136 | + |
| 1137 | + |
| 1138 | + function HandleAllTags() { |
| 1139 | + |
| 1140 | + // key name => array (tag name, character encoding) |
| 1141 | + static $tags; |
| 1142 | + if (empty($tags)) { |
| 1143 | + $tags = array( |
| 1144 | + 'asf' => array('asf' , 'UTF-16LE'), |
| 1145 | + 'midi' => array('midi' , 'ISO-8859-1'), |
| 1146 | + 'nsv' => array('nsv' , 'ISO-8859-1'), |
| 1147 | + 'ogg' => array('vorbiscomment' , 'UTF-8'), |
| 1148 | + 'png' => array('png' , 'UTF-8'), |
| 1149 | + 'tiff' => array('tiff' , 'ISO-8859-1'), |
| 1150 | + 'quicktime' => array('quicktime' , 'UTF-8'), |
| 1151 | + 'real' => array('real' , 'ISO-8859-1'), |
| 1152 | + 'vqf' => array('vqf' , 'ISO-8859-1'), |
| 1153 | + 'zip' => array('zip' , 'ISO-8859-1'), |
| 1154 | + 'riff' => array('riff' , 'ISO-8859-1'), |
| 1155 | + 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), |
| 1156 | + 'id3v1' => array('id3v1' , $this->encoding_id3v1), |
| 1157 | + 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 |
| 1158 | + 'ape' => array('ape' , 'UTF-8'), |
| 1159 | + 'cue' => array('cue' , 'ISO-8859-1'), |
| 1160 | + ); |
| 1161 | + } |
| 1162 | + |
| 1163 | + // loop thru comments array |
| 1164 | + foreach ($tags as $comment_name => $tagname_encoding_array) { |
| 1165 | + list($tag_name, $encoding) = $tagname_encoding_array; |
| 1166 | + |
| 1167 | + // fill in default encoding type if not already present |
| 1168 | + if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { |
| 1169 | + $this->info[$comment_name]['encoding'] = $encoding; |
| 1170 | + } |
| 1171 | + |
| 1172 | + // copy comments if key name set |
| 1173 | + if (!empty($this->info[$comment_name]['comments'])) { |
| 1174 | + |
| 1175 | + foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { |
| 1176 | + foreach ($valuearray as $key => $value) { |
| 1177 | + if (strlen(trim($value)) > 0) { |
| 1178 | + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; // do not trim!! Unicode characters will get mangled if trailing nulls are removed! |
| 1179 | + } |
| 1180 | + } |
| 1181 | + } |
| 1182 | + |
| 1183 | + if (!isset($this->info['tags'][$tag_name])) { |
| 1184 | + // comments are set but contain nothing but empty strings, so skip |
| 1185 | + continue; |
| 1186 | + } |
| 1187 | + |
| 1188 | + if ($this->option_tags_html) { |
| 1189 | + foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { |
| 1190 | + foreach ($valuearray as $key => $value) { |
| 1191 | + if (is_string($value)) { |
| 1192 | + //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); |
| 1193 | + $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $encoding)); |
| 1194 | + } else { |
| 1195 | + $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; |
| 1196 | + } |
| 1197 | + } |
| 1198 | + } |
| 1199 | + } |
| 1200 | + |
| 1201 | + $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! |
| 1202 | + } |
| 1203 | + |
| 1204 | + } |
| 1205 | + return true; |
| 1206 | + } |
| 1207 | + |
| 1208 | + |
| 1209 | + function getHashdata($algorithm) { |
| 1210 | + switch ($algorithm) { |
| 1211 | + case 'md5': |
| 1212 | + case 'sha1': |
| 1213 | + break; |
| 1214 | + |
| 1215 | + default: |
| 1216 | + return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); |
| 1217 | + break; |
| 1218 | + } |
| 1219 | + |
| 1220 | + if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { |
| 1221 | + |
| 1222 | + // We cannot get an identical md5_data value for Ogg files where the comments |
| 1223 | + // span more than 1 Ogg page (compared to the same audio data with smaller |
| 1224 | + // comments) using the normal getID3() method of MD5'ing the data between the |
| 1225 | + // end of the comments and the end of the file (minus any trailing tags), |
| 1226 | + // because the page sequence numbers of the pages that the audio data is on |
| 1227 | + // do not match. Under normal circumstances, where comments are smaller than |
| 1228 | + // the nominal 4-8kB page size, then this is not a problem, but if there are |
| 1229 | + // very large comments, the only way around it is to strip off the comment |
| 1230 | + // tags with vorbiscomment and MD5 that file. |
| 1231 | + // This procedure must be applied to ALL Ogg files, not just the ones with |
| 1232 | + // comments larger than 1 page, because the below method simply MD5's the |
| 1233 | + // whole file with the comments stripped, not just the portion after the |
| 1234 | + // comments block (which is the standard getID3() method. |
| 1235 | + |
| 1236 | + // The above-mentioned problem of comments spanning multiple pages and changing |
| 1237 | + // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but |
| 1238 | + // currently vorbiscomment only works on OggVorbis files. |
| 1239 | + |
| 1240 | + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { |
| 1241 | + |
| 1242 | + $this->info['warning'][] = 'Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'; |
| 1243 | + $this->info[$algorithm.'_data'] = false; |
| 1244 | + |
| 1245 | + } else { |
| 1246 | + |
| 1247 | + // Prevent user from aborting script |
| 1248 | + $old_abort = ignore_user_abort(true); |
| 1249 | + |
| 1250 | + // Create empty file |
| 1251 | + $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); |
| 1252 | + touch($empty); |
| 1253 | + |
| 1254 | + // Use vorbiscomment to make temp file without comments |
| 1255 | + $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); |
| 1256 | + $file = $this->info['filenamepath']; |
| 1257 | + |
| 1258 | + if (GETID3_OS_ISWINDOWS) { |
| 1259 | + |
| 1260 | + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { |
| 1261 | + |
| 1262 | + $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; |
| 1263 | + $VorbisCommentError = `$commandline`; |
| 1264 | + |
| 1265 | + } else { |
| 1266 | + |
| 1267 | + $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; |
| 1268 | + |
| 1269 | + } |
| 1270 | + |
| 1271 | + } else { |
| 1272 | + |
| 1273 | + $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; |
| 1274 | + $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; |
| 1275 | + $VorbisCommentError = `$commandline`; |
| 1276 | + |
| 1277 | + } |
| 1278 | + |
| 1279 | + if (!empty($VorbisCommentError)) { |
| 1280 | + |
| 1281 | + $this->info['warning'][] = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError; |
| 1282 | + $this->info[$algorithm.'_data'] = false; |
| 1283 | + |
| 1284 | + } else { |
| 1285 | + |
| 1286 | + // Get hash of newly created file |
| 1287 | + switch ($algorithm) { |
| 1288 | + case 'md5': |
| 1289 | + $this->info[$algorithm.'_data'] = getid3_lib::md5_file($temp); |
| 1290 | + break; |
| 1291 | + |
| 1292 | + case 'sha1': |
| 1293 | + $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($temp); |
| 1294 | + break; |
| 1295 | + } |
| 1296 | + } |
| 1297 | + |
| 1298 | + // Clean up |
| 1299 | + unlink($empty); |
| 1300 | + unlink($temp); |
| 1301 | + |
| 1302 | + // Reset abort setting |
| 1303 | + ignore_user_abort($old_abort); |
| 1304 | + |
| 1305 | + } |
| 1306 | + |
| 1307 | + } else { |
| 1308 | + |
| 1309 | + if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { |
| 1310 | + |
| 1311 | + // get hash from part of file |
| 1312 | + $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); |
| 1313 | + |
| 1314 | + } else { |
| 1315 | + |
| 1316 | + // get hash from whole file |
| 1317 | + switch ($algorithm) { |
| 1318 | + case 'md5': |
| 1319 | + $this->info[$algorithm.'_data'] = getid3_lib::md5_file($this->info['filenamepath']); |
| 1320 | + break; |
| 1321 | + |
| 1322 | + case 'sha1': |
| 1323 | + $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($this->info['filenamepath']); |
| 1324 | + break; |
| 1325 | + } |
| 1326 | + } |
| 1327 | + |
| 1328 | + } |
| 1329 | + return true; |
| 1330 | + } |
| 1331 | + |
| 1332 | + |
| 1333 | + function ChannelsBitratePlaytimeCalculations() { |
| 1334 | + |
| 1335 | + // set channelmode on audio |
| 1336 | + if (!isset($this->info['audio']['channels'])) { |
| 1337 | + // ignore |
| 1338 | + } elseif ($this->info['audio']['channels'] == 1) { |
| 1339 | + $this->info['audio']['channelmode'] = 'mono'; |
| 1340 | + } elseif ($this->info['audio']['channels'] == 2) { |
| 1341 | + $this->info['audio']['channelmode'] = 'stereo'; |
| 1342 | + } |
| 1343 | + |
| 1344 | + // Calculate combined bitrate - audio + video |
| 1345 | + $CombinedBitrate = 0; |
| 1346 | + $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); |
| 1347 | + $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); |
| 1348 | + if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { |
| 1349 | + $this->info['bitrate'] = $CombinedBitrate; |
| 1350 | + } |
| 1351 | + //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { |
| 1352 | + // // for example, VBR MPEG video files cannot determine video bitrate: |
| 1353 | + // // should not set overall bitrate and playtime from audio bitrate only |
| 1354 | + // unset($this->info['bitrate']); |
| 1355 | + //} |
| 1356 | + |
| 1357 | + // video bitrate undetermined, but calculable |
| 1358 | + if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { |
| 1359 | + // if video bitrate not set |
| 1360 | + if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { |
| 1361 | + // AND if audio bitrate is set to same as overall bitrate |
| 1362 | + if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { |
| 1363 | + // AND if playtime is set |
| 1364 | + if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { |
| 1365 | + // AND if AV data offset start/end is known |
| 1366 | + // THEN we can calculate the video bitrate |
| 1367 | + $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); |
| 1368 | + $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; |
| 1369 | + } |
| 1370 | + } |
| 1371 | + } |
| 1372 | + } |
| 1373 | + |
| 1374 | + if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { |
| 1375 | + $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; |
| 1376 | + } |
| 1377 | + |
| 1378 | + if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { |
| 1379 | + $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; |
| 1380 | + } |
| 1381 | + if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { |
| 1382 | + if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { |
| 1383 | + // audio only |
| 1384 | + $this->info['audio']['bitrate'] = $this->info['bitrate']; |
| 1385 | + } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { |
| 1386 | + // video only |
| 1387 | + $this->info['video']['bitrate'] = $this->info['bitrate']; |
| 1388 | + } |
| 1389 | + } |
| 1390 | + |
| 1391 | + // Set playtime string |
| 1392 | + if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { |
| 1393 | + $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); |
| 1394 | + } |
| 1395 | + } |
| 1396 | + |
| 1397 | + |
| 1398 | + function CalculateCompressionRatioVideo() { |
| 1399 | + if (empty($this->info['video'])) { |
| 1400 | + return false; |
| 1401 | + } |
| 1402 | + if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { |
| 1403 | + return false; |
| 1404 | + } |
| 1405 | + if (empty($this->info['video']['bits_per_sample'])) { |
| 1406 | + return false; |
| 1407 | + } |
| 1408 | + |
| 1409 | + switch ($this->info['video']['dataformat']) { |
| 1410 | + case 'bmp': |
| 1411 | + case 'gif': |
| 1412 | + case 'jpeg': |
| 1413 | + case 'jpg': |
| 1414 | + case 'png': |
| 1415 | + case 'tiff': |
| 1416 | + $FrameRate = 1; |
| 1417 | + $PlaytimeSeconds = 1; |
| 1418 | + $BitrateCompressed = $this->info['filesize'] * 8; |
| 1419 | + break; |
| 1420 | + |
| 1421 | + default: |
| 1422 | + if (!empty($this->info['video']['frame_rate'])) { |
| 1423 | + $FrameRate = $this->info['video']['frame_rate']; |
| 1424 | + } else { |
| 1425 | + return false; |
| 1426 | + } |
| 1427 | + if (!empty($this->info['playtime_seconds'])) { |
| 1428 | + $PlaytimeSeconds = $this->info['playtime_seconds']; |
| 1429 | + } else { |
| 1430 | + return false; |
| 1431 | + } |
| 1432 | + if (!empty($this->info['video']['bitrate'])) { |
| 1433 | + $BitrateCompressed = $this->info['video']['bitrate']; |
| 1434 | + } else { |
| 1435 | + return false; |
| 1436 | + } |
| 1437 | + break; |
| 1438 | + } |
| 1439 | + $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; |
| 1440 | + |
| 1441 | + $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; |
| 1442 | + return true; |
| 1443 | + } |
| 1444 | + |
| 1445 | + |
| 1446 | + function CalculateCompressionRatioAudio() { |
| 1447 | + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) { |
| 1448 | + return false; |
| 1449 | + } |
| 1450 | + $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); |
| 1451 | + |
| 1452 | + if (!empty($this->info['audio']['streams'])) { |
| 1453 | + foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { |
| 1454 | + if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { |
| 1455 | + $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); |
| 1456 | + } |
| 1457 | + } |
| 1458 | + } |
| 1459 | + return true; |
| 1460 | + } |
| 1461 | + |
| 1462 | + |
| 1463 | + function CalculateReplayGain() { |
| 1464 | + if (isset($this->info['replay_gain'])) { |
| 1465 | + $this->info['replay_gain']['reference_volume'] = 89; |
| 1466 | + if (isset($this->info['replay_gain']['track']['adjustment'])) { |
| 1467 | + $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; |
| 1468 | + } |
| 1469 | + if (isset($this->info['replay_gain']['album']['adjustment'])) { |
| 1470 | + $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; |
| 1471 | + } |
| 1472 | + |
| 1473 | + if (isset($this->info['replay_gain']['track']['peak'])) { |
| 1474 | + $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); |
| 1475 | + } |
| 1476 | + if (isset($this->info['replay_gain']['album']['peak'])) { |
| 1477 | + $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); |
| 1478 | + } |
| 1479 | + } |
| 1480 | + return true; |
| 1481 | + } |
| 1482 | + |
| 1483 | + function ProcessAudioStreams() { |
| 1484 | + if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { |
| 1485 | + if (!isset($this->info['audio']['streams'])) { |
| 1486 | + foreach ($this->info['audio'] as $key => $value) { |
| 1487 | + if ($key != 'streams') { |
| 1488 | + $this->info['audio']['streams'][0][$key] = $value; |
| 1489 | + } |
| 1490 | + } |
| 1491 | + } |
| 1492 | + } |
| 1493 | + return true; |
| 1494 | + } |
| 1495 | + |
| 1496 | + function getid3_tempnam() { |
| 1497 | + return tempnam($this->tempdir, 'gI3'); |
| 1498 | + } |
| 1499 | + |
| 1500 | +} |
| 1501 | + |
| 1502 | +?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/module.audio.ogg.php |
— | — | @@ -0,0 +1,565 @@ |
| 2 | +<?php |
| 3 | +///////////////////////////////////////////////////////////////// |
| 4 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 5 | +// available at http://getid3.sourceforge.net // |
| 6 | +// or http://www.getid3.org // |
| 7 | +///////////////////////////////////////////////////////////////// |
| 8 | +// See readme.txt for more details // |
| 9 | +///////////////////////////////////////////////////////////////// |
| 10 | +// // |
| 11 | +// module.audio.ogg.php // |
| 12 | +// module for analyzing Ogg Vorbis, OggFLAC and Speex files // |
| 13 | +// dependencies: module.audio.flac.php // |
| 14 | +// /// |
| 15 | +///////////////////////////////////////////////////////////////// |
| 16 | + |
| 17 | +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); |
| 18 | + |
| 19 | +class getid3_ogg |
| 20 | +{ |
| 21 | + |
| 22 | + function getid3_ogg(&$fd, &$ThisFileInfo) { |
| 23 | + |
| 24 | + $ThisFileInfo['fileformat'] = 'ogg'; |
| 25 | + |
| 26 | + // Warn about illegal tags - only vorbiscomments are allowed |
| 27 | + if (isset($ThisFileInfo['id3v2'])) { |
| 28 | + $ThisFileInfo['warning'][] = 'Illegal ID3v2 tag present.'; |
| 29 | + } |
| 30 | + if (isset($ThisFileInfo['id3v1'])) { |
| 31 | + $ThisFileInfo['warning'][] = 'Illegal ID3v1 tag present.'; |
| 32 | + } |
| 33 | + if (isset($ThisFileInfo['ape'])) { |
| 34 | + $ThisFileInfo['warning'][] = 'Illegal APE tag present.'; |
| 35 | + } |
| 36 | + |
| 37 | + |
| 38 | + // Page 1 - Stream Header |
| 39 | + |
| 40 | + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); |
| 41 | + |
| 42 | + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); |
| 43 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
| 44 | + |
| 45 | + if (ftell($fd) >= GETID3_FREAD_BUFFER_SIZE) { |
| 46 | + $ThisFileInfo['error'][] = 'Could not find start of Ogg page in the first '.GETID3_FREAD_BUFFER_SIZE.' bytes (this might not be an Ogg-Vorbis file?)'; |
| 47 | + unset($ThisFileInfo['fileformat']); |
| 48 | + unset($ThisFileInfo['ogg']); |
| 49 | + return false; |
| 50 | + } |
| 51 | + |
| 52 | + $filedata = fread($fd, $oggpageinfo['page_length']); |
| 53 | + $filedataoffset = 0; |
| 54 | + |
| 55 | + if (substr($filedata, 0, 4) == 'fLaC') { |
| 56 | + |
| 57 | + $ThisFileInfo['audio']['dataformat'] = 'flac'; |
| 58 | + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
| 59 | + $ThisFileInfo['audio']['lossless'] = true; |
| 60 | + |
| 61 | + } elseif (substr($filedata, 1, 6) == 'vorbis') { |
| 62 | + |
| 63 | + $this->ParseVorbisPageHeader($filedata, $filedataoffset, $ThisFileInfo, $oggpageinfo); |
| 64 | + |
| 65 | + } elseif (substr($filedata, 0, 8) == 'Speex ') { |
| 66 | + |
| 67 | + // http://www.speex.org/manual/node10.html |
| 68 | + |
| 69 | + $ThisFileInfo['audio']['dataformat'] = 'speex'; |
| 70 | + $ThisFileInfo['mime_type'] = 'audio/speex'; |
| 71 | + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; |
| 72 | + $ThisFileInfo['audio']['lossless'] = false; |
| 73 | + |
| 74 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' |
| 75 | + $filedataoffset += 8; |
| 76 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); |
| 77 | + $filedataoffset += 20; |
| 78 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 79 | + $filedataoffset += 4; |
| 80 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 81 | + $filedataoffset += 4; |
| 82 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 83 | + $filedataoffset += 4; |
| 84 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 85 | + $filedataoffset += 4; |
| 86 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 87 | + $filedataoffset += 4; |
| 88 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 89 | + $filedataoffset += 4; |
| 90 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 91 | + $filedataoffset += 4; |
| 92 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 93 | + $filedataoffset += 4; |
| 94 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 95 | + $filedataoffset += 4; |
| 96 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 97 | + $filedataoffset += 4; |
| 98 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 99 | + $filedataoffset += 4; |
| 100 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 101 | + $filedataoffset += 4; |
| 102 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 103 | + $filedataoffset += 4; |
| 104 | + |
| 105 | + $ThisFileInfo['speex']['speex_version'] = trim($ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); |
| 106 | + $ThisFileInfo['speex']['sample_rate'] = $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; |
| 107 | + $ThisFileInfo['speex']['channels'] = $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; |
| 108 | + $ThisFileInfo['speex']['vbr'] = (bool) $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; |
| 109 | + $ThisFileInfo['speex']['band_type'] = getid3_ogg::SpeexBandModeLookup($ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); |
| 110 | + |
| 111 | + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['speex']['sample_rate']; |
| 112 | + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['speex']['channels']; |
| 113 | + if ($ThisFileInfo['speex']['vbr']) { |
| 114 | + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
| 115 | + } |
| 116 | + |
| 117 | + } else { |
| 118 | + |
| 119 | + $ThisFileInfo['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found neither'; |
| 120 | + unset($ThisFileInfo['ogg']); |
| 121 | + unset($ThisFileInfo['mime_type']); |
| 122 | + return false; |
| 123 | + |
| 124 | + } |
| 125 | + |
| 126 | + |
| 127 | + // Page 2 - Comment Header |
| 128 | + |
| 129 | + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); |
| 130 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
| 131 | + |
| 132 | + switch ($ThisFileInfo['audio']['dataformat']) { |
| 133 | + |
| 134 | + case 'vorbis': |
| 135 | + $filedata = fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
| 136 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); |
| 137 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' |
| 138 | + |
| 139 | + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); |
| 140 | + break; |
| 141 | + |
| 142 | + case 'flac': |
| 143 | + if (!getid3_flac::FLACparseMETAdata($fd, $ThisFileInfo)) { |
| 144 | + $ThisFileInfo['error'][] = 'Failed to parse FLAC headers'; |
| 145 | + return false; |
| 146 | + } |
| 147 | + break; |
| 148 | + |
| 149 | + case 'speex': |
| 150 | + fseek($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); |
| 151 | + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); |
| 152 | + break; |
| 153 | + |
| 154 | + } |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | + // Last Page - Number of Samples |
| 159 | + |
| 160 | + if ($ThisFileInfo['avdataend'] >= pow(2, 31)) { |
| 161 | + |
| 162 | + $ThisFileInfo['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond 2GB)'; |
| 163 | + |
| 164 | + } else { |
| 165 | + |
| 166 | + fseek($fd, max($ThisFileInfo['avdataend'] - GETID3_FREAD_BUFFER_SIZE, 0), SEEK_SET); |
| 167 | + $LastChunkOfOgg = strrev(fread($fd, GETID3_FREAD_BUFFER_SIZE)); |
| 168 | + if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { |
| 169 | + fseek($fd, $ThisFileInfo['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET); |
| 170 | + $ThisFileInfo['avdataend'] = ftell($fd); |
| 171 | + $ThisFileInfo['ogg']['pageheader']['eos'] = getid3_ogg::ParseOggPageHeader($fd); |
| 172 | + $ThisFileInfo['ogg']['samples'] = $ThisFileInfo['ogg']['pageheader']['eos']['pcm_abs_position']; |
| 173 | + if ($ThisFileInfo['ogg']['samples'] == 0) { |
| 174 | + $ThisFileInfo['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; |
| 175 | + return false; |
| 176 | + } |
| 177 | + $ThisFileInfo['ogg']['bitrate_average'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($ThisFileInfo['ogg']['samples'] / $ThisFileInfo['audio']['sample_rate']); |
| 178 | + } |
| 179 | + |
| 180 | + } |
| 181 | + |
| 182 | + if (!empty($ThisFileInfo['ogg']['bitrate_average'])) { |
| 183 | + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['ogg']['bitrate_average']; |
| 184 | + } elseif (!empty($ThisFileInfo['ogg']['bitrate_nominal'])) { |
| 185 | + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['ogg']['bitrate_nominal']; |
| 186 | + } elseif (!empty($ThisFileInfo['ogg']['bitrate_min']) && !empty($ThisFileInfo['ogg']['bitrate_max'])) { |
| 187 | + $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['ogg']['bitrate_min'] + $ThisFileInfo['ogg']['bitrate_max']) / 2; |
| 188 | + } |
| 189 | + if (isset($ThisFileInfo['audio']['bitrate']) && !isset($ThisFileInfo['playtime_seconds'])) { |
| 190 | + if ($ThisFileInfo['audio']['bitrate'] == 0) { |
| 191 | + $ThisFileInfo['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; |
| 192 | + return false; |
| 193 | + } |
| 194 | + $ThisFileInfo['playtime_seconds'] = (float) ((($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']); |
| 195 | + } |
| 196 | + |
| 197 | + if (isset($ThisFileInfo['ogg']['vendor'])) { |
| 198 | + $ThisFileInfo['audio']['encoder'] = preg_replace('/^Encoded with /', '', $ThisFileInfo['ogg']['vendor']); |
| 199 | + |
| 200 | + // Vorbis only |
| 201 | + if ($ThisFileInfo['audio']['dataformat'] == 'vorbis') { |
| 202 | + |
| 203 | + // Vorbis 1.0 starts with Xiph.Org |
| 204 | + if (preg_match('/^Xiph.Org/', $ThisFileInfo['audio']['encoder'])) { |
| 205 | + |
| 206 | + if ($ThisFileInfo['audio']['bitrate_mode'] == 'abr') { |
| 207 | + |
| 208 | + // Set -b 128 on abr files |
| 209 | + $ThisFileInfo['audio']['encoder_options'] = '-b '.round($ThisFileInfo['ogg']['bitrate_nominal'] / 1000); |
| 210 | + |
| 211 | + } elseif (($ThisFileInfo['audio']['bitrate_mode'] == 'vbr') && ($ThisFileInfo['audio']['channels'] == 2) && ($ThisFileInfo['audio']['sample_rate'] >= 44100) && ($ThisFileInfo['audio']['sample_rate'] <= 48000)) { |
| 212 | + // Set -q N on vbr files |
| 213 | + $ThisFileInfo['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($ThisFileInfo['ogg']['bitrate_nominal']); |
| 214 | + |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + if (empty($ThisFileInfo['audio']['encoder_options']) && !empty($ThisFileInfo['ogg']['bitrate_nominal'])) { |
| 219 | + $ThisFileInfo['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($ThisFileInfo['ogg']['bitrate_nominal'] / 1000)).'kbps'; |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + return true; |
| 225 | + } |
| 226 | + |
| 227 | + static function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$ThisFileInfo, &$oggpageinfo) { |
| 228 | + $ThisFileInfo['audio']['dataformat'] = 'vorbis'; |
| 229 | + $ThisFileInfo['audio']['lossless'] = false; |
| 230 | + |
| 231 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 232 | + $filedataoffset += 1; |
| 233 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' |
| 234 | + $filedataoffset += 6; |
| 235 | + $ThisFileInfo['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 236 | + $filedataoffset += 4; |
| 237 | + $ThisFileInfo['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 238 | + $filedataoffset += 1; |
| 239 | + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['ogg']['numberofchannels']; |
| 240 | + $ThisFileInfo['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 241 | + $filedataoffset += 4; |
| 242 | + if ($ThisFileInfo['ogg']['samplerate'] == 0) { |
| 243 | + $ThisFileInfo['error'][] = 'Corrupt Ogg file: sample rate == zero'; |
| 244 | + return false; |
| 245 | + } |
| 246 | + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['ogg']['samplerate']; |
| 247 | + $ThisFileInfo['ogg']['samples'] = 0; // filled in later |
| 248 | + $ThisFileInfo['ogg']['bitrate_average'] = 0; // filled in later |
| 249 | + $ThisFileInfo['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 250 | + $filedataoffset += 4; |
| 251 | + $ThisFileInfo['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 252 | + $filedataoffset += 4; |
| 253 | + $ThisFileInfo['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 254 | + $filedataoffset += 4; |
| 255 | + $ThisFileInfo['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); |
| 256 | + $ThisFileInfo['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); |
| 257 | + $ThisFileInfo['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet |
| 258 | + |
| 259 | + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr |
| 260 | + if ($ThisFileInfo['ogg']['bitrate_max'] == 0xFFFFFFFF) { |
| 261 | + unset($ThisFileInfo['ogg']['bitrate_max']); |
| 262 | + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; |
| 263 | + } |
| 264 | + if ($ThisFileInfo['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { |
| 265 | + unset($ThisFileInfo['ogg']['bitrate_nominal']); |
| 266 | + } |
| 267 | + if ($ThisFileInfo['ogg']['bitrate_min'] == 0xFFFFFFFF) { |
| 268 | + unset($ThisFileInfo['ogg']['bitrate_min']); |
| 269 | + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; |
| 270 | + } |
| 271 | + return true; |
| 272 | + } |
| 273 | + |
| 274 | + static function ParseOggPageHeader(&$fd) { |
| 275 | + // http://xiph.org/ogg/vorbis/doc/framing.html |
| 276 | + $oggheader['page_start_offset'] = ftell($fd); // where we started from in the file |
| 277 | + |
| 278 | + $filedata = fread($fd, GETID3_FREAD_BUFFER_SIZE); |
| 279 | + $filedataoffset = 0; |
| 280 | + while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { |
| 281 | + if ((ftell($fd) - $oggheader['page_start_offset']) >= GETID3_FREAD_BUFFER_SIZE) { |
| 282 | + // should be found before here |
| 283 | + return false; |
| 284 | + } |
| 285 | + if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { |
| 286 | + if (feof($fd) || (($filedata .= fread($fd, GETID3_FREAD_BUFFER_SIZE)) === false)) { |
| 287 | + // get some more data, unless eof, in which case fail |
| 288 | + return false; |
| 289 | + } |
| 290 | + } |
| 291 | + } |
| 292 | + $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' |
| 293 | + |
| 294 | + $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 295 | + $filedataoffset += 1; |
| 296 | + $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 297 | + $filedataoffset += 1; |
| 298 | + $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet |
| 299 | + $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) |
| 300 | + $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) |
| 301 | + |
| 302 | + $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
| 303 | + $filedataoffset += 8; |
| 304 | + $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 305 | + $filedataoffset += 4; |
| 306 | + $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 307 | + $filedataoffset += 4; |
| 308 | + $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
| 309 | + $filedataoffset += 4; |
| 310 | + $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 311 | + $filedataoffset += 1; |
| 312 | + $oggheader['page_length'] = 0; |
| 313 | + for ($i = 0; $i < $oggheader['page_segments']; $i++) { |
| 314 | + $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
| 315 | + $filedataoffset += 1; |
| 316 | + $oggheader['page_length'] += $oggheader['segment_table'][$i]; |
| 317 | + } |
| 318 | + $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; |
| 319 | + $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; |
| 320 | + fseek($fd, $oggheader['header_end_offset'], SEEK_SET); |
| 321 | + |
| 322 | + return $oggheader; |
| 323 | + } |
| 324 | + |
| 325 | + |
| 326 | + static function ParseVorbisCommentsFilepointer(&$fd, &$ThisFileInfo) { |
| 327 | + |
| 328 | + $OriginalOffset = ftell($fd); |
| 329 | + $CommentStartOffset = $OriginalOffset; |
| 330 | + $commentdataoffset = 0; |
| 331 | + $VorbisCommentPage = 1; |
| 332 | + |
| 333 | + switch ($ThisFileInfo['audio']['dataformat']) { |
| 334 | + case 'vorbis': |
| 335 | + $CommentStartOffset = $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block |
| 336 | + fseek($fd, $CommentStartOffset, SEEK_SET); |
| 337 | + $commentdataoffset = 27 + $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; |
| 338 | + $commentdata = fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); |
| 339 | + |
| 340 | + $commentdataoffset += (strlen('vorbis') + 1); |
| 341 | + break; |
| 342 | + |
| 343 | + case 'flac': |
| 344 | + fseek($fd, $ThisFileInfo['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET); |
| 345 | + $commentdata = fread($fd, $ThisFileInfo['flac']['VORBIS_COMMENT']['raw']['block_length']); |
| 346 | + break; |
| 347 | + |
| 348 | + case 'speex': |
| 349 | + $CommentStartOffset = $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block |
| 350 | + fseek($fd, $CommentStartOffset, SEEK_SET); |
| 351 | + $commentdataoffset = 27 + $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; |
| 352 | + $commentdata = fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); |
| 353 | + break; |
| 354 | + |
| 355 | + default: |
| 356 | + return false; |
| 357 | + break; |
| 358 | + } |
| 359 | + |
| 360 | + $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
| 361 | + $commentdataoffset += 4; |
| 362 | + |
| 363 | + $ThisFileInfo['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); |
| 364 | + $commentdataoffset += $VendorSize; |
| 365 | + |
| 366 | + $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
| 367 | + $commentdataoffset += 4; |
| 368 | + $ThisFileInfo['avdataoffset'] = $CommentStartOffset + $commentdataoffset; |
| 369 | + |
| 370 | + $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); |
| 371 | + for ($i = 0; $i < $CommentsCount; $i++) { |
| 372 | + |
| 373 | + $ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; |
| 374 | + |
| 375 | + if (ftell($fd) < ($ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] + 4)) { |
| 376 | + $VorbisCommentPage++; |
| 377 | + |
| 378 | + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); |
| 379 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
| 380 | + |
| 381 | + // First, save what we haven't read yet |
| 382 | + $AsYetUnusedData = substr($commentdata, $commentdataoffset); |
| 383 | + |
| 384 | + // Then take that data off the end |
| 385 | + $commentdata = substr($commentdata, 0, $commentdataoffset); |
| 386 | + |
| 387 | + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct |
| 388 | + $commentdata .= str_repeat("\x00", 27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
| 389 | + $commentdataoffset += (27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
| 390 | + |
| 391 | + // Finally, stick the unused data back on the end |
| 392 | + $commentdata .= $AsYetUnusedData; |
| 393 | + |
| 394 | + //$commentdata .= fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
| 395 | + $commentdata .= fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1)); |
| 396 | + |
| 397 | + } |
| 398 | + $ThisFileInfo['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
| 399 | + |
| 400 | + // replace avdataoffset with position just after the last vorbiscomment |
| 401 | + $ThisFileInfo['avdataoffset'] = $ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] + $ThisFileInfo['ogg']['comments_raw'][$i]['size'] + 4; |
| 402 | + |
| 403 | + $commentdataoffset += 4; |
| 404 | + while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo['ogg']['comments_raw'][$i]['size']) { |
| 405 | + if (($ThisFileInfo['ogg']['comments_raw'][$i]['size'] > $ThisFileInfo['avdataend']) || ($ThisFileInfo['ogg']['comments_raw'][$i]['size'] < 0)) { |
| 406 | + $ThisFileInfo['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo['ogg']['comments_raw'][$i]['size']).' bytes) - aborting reading comments'; |
| 407 | + break 2; |
| 408 | + } |
| 409 | + |
| 410 | + $VorbisCommentPage++; |
| 411 | + |
| 412 | + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); |
| 413 | + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
| 414 | + |
| 415 | + // First, save what we haven't read yet |
| 416 | + $AsYetUnusedData = substr($commentdata, $commentdataoffset); |
| 417 | + |
| 418 | + // Then take that data off the end |
| 419 | + $commentdata = substr($commentdata, 0, $commentdataoffset); |
| 420 | + |
| 421 | + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct |
| 422 | + $commentdata .= str_repeat("\x00", 27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
| 423 | + $commentdataoffset += (27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
| 424 | + |
| 425 | + // Finally, stick the unused data back on the end |
| 426 | + $commentdata .= $AsYetUnusedData; |
| 427 | + |
| 428 | + //$commentdata .= fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
| 429 | + if (!isset($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage])) { |
| 430 | + $ThisFileInfo['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($fd); |
| 431 | + break; |
| 432 | + } |
| 433 | + $readlength = getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1); |
| 434 | + if ($readlength <= 0) { |
| 435 | + $ThisFileInfo['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($fd); |
| 436 | + break; |
| 437 | + } |
| 438 | + $commentdata .= fread($fd, $readlength); |
| 439 | + |
| 440 | + //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; |
| 441 | + } |
| 442 | + $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo['ogg']['comments_raw'][$i]['size']); |
| 443 | + $commentdataoffset += $ThisFileInfo['ogg']['comments_raw'][$i]['size']; |
| 444 | + |
| 445 | + if (!$commentstring) { |
| 446 | + |
| 447 | + // no comment? |
| 448 | + $ThisFileInfo['warning'][] = 'Blank Ogg comment ['.$i.']'; |
| 449 | + |
| 450 | + } elseif (strstr($commentstring, '=')) { |
| 451 | + |
| 452 | + $commentexploded = explode('=', $commentstring, 2); |
| 453 | + $ThisFileInfo['ogg']['comments_raw'][$i]['key'] = strtoupper($commentexploded[0]); |
| 454 | + $ThisFileInfo['ogg']['comments_raw'][$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); |
| 455 | + $ThisFileInfo['ogg']['comments_raw'][$i]['data'] = base64_decode($ThisFileInfo['ogg']['comments_raw'][$i]['value']); |
| 456 | + |
| 457 | + $ThisFileInfo['ogg']['comments'][strtolower($ThisFileInfo['ogg']['comments_raw'][$i]['key'])][] = $ThisFileInfo['ogg']['comments_raw'][$i]['value']; |
| 458 | + |
| 459 | + $imageinfo = array(); |
| 460 | + $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo['ogg']['comments_raw'][$i]['data'], $imageinfo); |
| 461 | + $ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] = getid3_lib::image_type_to_mime_type($imagechunkcheck[2]); |
| 462 | + if (!$ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] || ($ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) { |
| 463 | + unset($ThisFileInfo['ogg']['comments_raw'][$i]['image_mime']); |
| 464 | + unset($ThisFileInfo['ogg']['comments_raw'][$i]['data']); |
| 465 | + } |
| 466 | + |
| 467 | + } else { |
| 468 | + |
| 469 | + $ThisFileInfo['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; |
| 470 | + |
| 471 | + } |
| 472 | + } |
| 473 | + |
| 474 | + |
| 475 | + // Replay Gain Adjustment |
| 476 | + // http://privatewww.essex.ac.uk/~djmrob/replaygain/ |
| 477 | + if (isset($ThisFileInfo['ogg']['comments']) && is_array($ThisFileInfo['ogg']['comments'])) { |
| 478 | + foreach ($ThisFileInfo['ogg']['comments'] as $index => $commentvalue) { |
| 479 | + switch ($index) { |
| 480 | + case 'rg_audiophile': |
| 481 | + case 'replaygain_album_gain': |
| 482 | + $ThisFileInfo['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; |
| 483 | + unset($ThisFileInfo['ogg']['comments'][$index]); |
| 484 | + break; |
| 485 | + |
| 486 | + case 'rg_radio': |
| 487 | + case 'replaygain_track_gain': |
| 488 | + $ThisFileInfo['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; |
| 489 | + unset($ThisFileInfo['ogg']['comments'][$index]); |
| 490 | + break; |
| 491 | + |
| 492 | + case 'replaygain_album_peak': |
| 493 | + $ThisFileInfo['replay_gain']['album']['peak'] = (double) $commentvalue[0]; |
| 494 | + unset($ThisFileInfo['ogg']['comments'][$index]); |
| 495 | + break; |
| 496 | + |
| 497 | + case 'rg_peak': |
| 498 | + case 'replaygain_track_peak': |
| 499 | + $ThisFileInfo['replay_gain']['track']['peak'] = (double) $commentvalue[0]; |
| 500 | + unset($ThisFileInfo['ogg']['comments'][$index]); |
| 501 | + break; |
| 502 | + |
| 503 | + |
| 504 | + default: |
| 505 | + // do nothing |
| 506 | + break; |
| 507 | + } |
| 508 | + } |
| 509 | + } |
| 510 | + |
| 511 | + fseek($fd, $OriginalOffset, SEEK_SET); |
| 512 | + |
| 513 | + return true; |
| 514 | + } |
| 515 | + |
| 516 | + static function SpeexBandModeLookup($mode) { |
| 517 | + static $SpeexBandModeLookup = array(); |
| 518 | + if (empty($SpeexBandModeLookup)) { |
| 519 | + $SpeexBandModeLookup[0] = 'narrow'; |
| 520 | + $SpeexBandModeLookup[1] = 'wide'; |
| 521 | + $SpeexBandModeLookup[2] = 'ultra-wide'; |
| 522 | + } |
| 523 | + return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); |
| 524 | + } |
| 525 | + |
| 526 | + |
| 527 | + static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { |
| 528 | + for ($i = 0; $i < $SegmentNumber; $i++) { |
| 529 | + $segmentlength = 0; |
| 530 | + foreach ($OggInfoArray['segment_table'] as $key => $value) { |
| 531 | + $segmentlength += $value; |
| 532 | + if ($value < 255) { |
| 533 | + break; |
| 534 | + } |
| 535 | + } |
| 536 | + } |
| 537 | + return $segmentlength; |
| 538 | + } |
| 539 | + |
| 540 | + |
| 541 | + static function get_quality_from_nominal_bitrate($nominal_bitrate) { |
| 542 | + |
| 543 | + // decrease precision |
| 544 | + $nominal_bitrate = $nominal_bitrate / 1000; |
| 545 | + |
| 546 | + if ($nominal_bitrate < 128) { |
| 547 | + // q-1 to q4 |
| 548 | + $qval = ($nominal_bitrate - 64) / 16; |
| 549 | + } elseif ($nominal_bitrate < 256) { |
| 550 | + // q4 to q8 |
| 551 | + $qval = $nominal_bitrate / 32; |
| 552 | + } elseif ($nominal_bitrate < 320) { |
| 553 | + // q8 to q9 |
| 554 | + $qval = ($nominal_bitrate + 256) / 64; |
| 555 | + } else { |
| 556 | + // q9 to q10 |
| 557 | + $qval = ($nominal_bitrate + 1300) / 180; |
| 558 | + } |
| 559 | + //return $qval; // 5.031324 |
| 560 | + //return intval($qval); // 5 |
| 561 | + return round($qval, 1); // 5 or 4.9 |
| 562 | + } |
| 563 | + |
| 564 | +} |
| 565 | + |
| 566 | +?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/getid3.lib.php |
— | — | @@ -0,0 +1,1340 @@ |
| 2 | +<?php |
| 3 | +///////////////////////////////////////////////////////////////// |
| 4 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 5 | +// available at http://getid3.sourceforge.net // |
| 6 | +// or http://www.getid3.org // |
| 7 | +///////////////////////////////////////////////////////////////// |
| 8 | +// // |
| 9 | +// getid3.lib.php - part of getID3() // |
| 10 | +// See readme.txt for more details // |
| 11 | +// /// |
| 12 | +///////////////////////////////////////////////////////////////// |
| 13 | + |
| 14 | + |
| 15 | +class getid3_lib |
| 16 | +{ |
| 17 | + |
| 18 | + static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlsafe=true) { |
| 19 | + $returnstring = ''; |
| 20 | + for ($i = 0; $i < strlen($string); $i++) { |
| 21 | + if ($hex) { |
| 22 | + $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); |
| 23 | + } else { |
| 24 | + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '€'); |
| 25 | + } |
| 26 | + if ($spaces) { |
| 27 | + $returnstring .= ' '; |
| 28 | + } |
| 29 | + } |
| 30 | + if ($htmlsafe) { |
| 31 | + $returnstring = htmlentities($returnstring); |
| 32 | + } |
| 33 | + return $returnstring; |
| 34 | + } |
| 35 | + |
| 36 | + static function trunc($floatnumber) { |
| 37 | + // truncates a floating-point number at the decimal point |
| 38 | + // returns int (if possible, otherwise float) |
| 39 | + if ($floatnumber >= 1) { |
| 40 | + $truncatednumber = floor($floatnumber); |
| 41 | + } elseif ($floatnumber <= -1) { |
| 42 | + $truncatednumber = ceil($floatnumber); |
| 43 | + } else { |
| 44 | + $truncatednumber = 0; |
| 45 | + } |
| 46 | + if ($truncatednumber <= 1073741824) { // 2^30 |
| 47 | + $truncatednumber = (int) $truncatednumber; |
| 48 | + } |
| 49 | + return $truncatednumber; |
| 50 | + } |
| 51 | + |
| 52 | + |
| 53 | + static function safe_inc(&$variable, $increment=1) { |
| 54 | + if (isset($variable)) { |
| 55 | + $variable += $increment; |
| 56 | + } else { |
| 57 | + $variable = $increment; |
| 58 | + } |
| 59 | + return true; |
| 60 | + } |
| 61 | + |
| 62 | + static function CastAsInt($floatnum) { |
| 63 | + // convert to float if not already |
| 64 | + $floatnum = (float) $floatnum; |
| 65 | + |
| 66 | + // convert a float to type int, only if possible |
| 67 | + if (getid3_lib::trunc($floatnum) == $floatnum) { |
| 68 | + // it's not floating point |
| 69 | + if ($floatnum <= 2147483647) { // 2^31 |
| 70 | + // it's within int range |
| 71 | + $floatnum = (int) $floatnum; |
| 72 | + } |
| 73 | + } |
| 74 | + return $floatnum; |
| 75 | + } |
| 76 | + |
| 77 | + |
| 78 | + static function DecimalizeFraction($fraction) { |
| 79 | + list($numerator, $denominator) = explode('/', $fraction); |
| 80 | + return $numerator / ($denominator ? $denominator : 1); |
| 81 | + } |
| 82 | + |
| 83 | + |
| 84 | + static function DecimalBinary2Float($binarynumerator) { |
| 85 | + $numerator = getid3_lib::Bin2Dec($binarynumerator); |
| 86 | + $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); |
| 87 | + return ($numerator / $denominator); |
| 88 | + } |
| 89 | + |
| 90 | + |
| 91 | + static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { |
| 92 | + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html |
| 93 | + if (strpos($binarypointnumber, '.') === false) { |
| 94 | + $binarypointnumber = '0.'.$binarypointnumber; |
| 95 | + } elseif ($binarypointnumber{0} == '.') { |
| 96 | + $binarypointnumber = '0'.$binarypointnumber; |
| 97 | + } |
| 98 | + $exponent = 0; |
| 99 | + while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { |
| 100 | + if (substr($binarypointnumber, 1, 1) == '.') { |
| 101 | + $exponent--; |
| 102 | + $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); |
| 103 | + } else { |
| 104 | + $pointpos = strpos($binarypointnumber, '.'); |
| 105 | + $exponent += ($pointpos - 1); |
| 106 | + $binarypointnumber = str_replace('.', '', $binarypointnumber); |
| 107 | + $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); |
| 108 | + } |
| 109 | + } |
| 110 | + $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); |
| 111 | + return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); |
| 112 | + } |
| 113 | + |
| 114 | + |
| 115 | + static function Float2BinaryDecimal($floatvalue) { |
| 116 | + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html |
| 117 | + $maxbits = 128; // to how many bits of precision should the calculations be taken? |
| 118 | + $intpart = getid3_lib::trunc($floatvalue); |
| 119 | + $floatpart = abs($floatvalue - $intpart); |
| 120 | + $pointbitstring = ''; |
| 121 | + while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { |
| 122 | + $floatpart *= 2; |
| 123 | + $pointbitstring .= (string) getid3_lib::trunc($floatpart); |
| 124 | + $floatpart -= getid3_lib::trunc($floatpart); |
| 125 | + } |
| 126 | + $binarypointnumber = decbin($intpart).'.'.$pointbitstring; |
| 127 | + return $binarypointnumber; |
| 128 | + } |
| 129 | + |
| 130 | + |
| 131 | + static function Float2String($floatvalue, $bits) { |
| 132 | + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html |
| 133 | + switch ($bits) { |
| 134 | + case 32: |
| 135 | + $exponentbits = 8; |
| 136 | + $fractionbits = 23; |
| 137 | + break; |
| 138 | + |
| 139 | + case 64: |
| 140 | + $exponentbits = 11; |
| 141 | + $fractionbits = 52; |
| 142 | + break; |
| 143 | + |
| 144 | + default: |
| 145 | + return false; |
| 146 | + break; |
| 147 | + } |
| 148 | + if ($floatvalue >= 0) { |
| 149 | + $signbit = '0'; |
| 150 | + } else { |
| 151 | + $signbit = '1'; |
| 152 | + } |
| 153 | + $normalizedbinary = getid3_lib::NormalizeBinaryPoint(getid3_lib::Float2BinaryDecimal($floatvalue), $fractionbits); |
| 154 | + $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent |
| 155 | + $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); |
| 156 | + $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); |
| 157 | + |
| 158 | + return getid3_lib::BigEndian2String(getid3_lib::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); |
| 159 | + } |
| 160 | + |
| 161 | + |
| 162 | + static function LittleEndian2Float($byteword) { |
| 163 | + return getid3_lib::BigEndian2Float(strrev($byteword)); |
| 164 | + } |
| 165 | + |
| 166 | + |
| 167 | + static function BigEndian2Float($byteword) { |
| 168 | + // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic |
| 169 | + // http://www.psc.edu/general/software/packages/ieee/ieee.html |
| 170 | + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html |
| 171 | + |
| 172 | + $bitword = getid3_lib::BigEndian2Bin($byteword); |
| 173 | + if (!$bitword) { |
| 174 | + return 0; |
| 175 | + } |
| 176 | + $signbit = $bitword{0}; |
| 177 | + |
| 178 | + switch (strlen($byteword) * 8) { |
| 179 | + case 32: |
| 180 | + $exponentbits = 8; |
| 181 | + $fractionbits = 23; |
| 182 | + break; |
| 183 | + |
| 184 | + case 64: |
| 185 | + $exponentbits = 11; |
| 186 | + $fractionbits = 52; |
| 187 | + break; |
| 188 | + |
| 189 | + case 80: |
| 190 | + // 80-bit Apple SANE format |
| 191 | + // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ |
| 192 | + $exponentstring = substr($bitword, 1, 15); |
| 193 | + $isnormalized = intval($bitword{16}); |
| 194 | + $fractionstring = substr($bitword, 17, 63); |
| 195 | + $exponent = pow(2, getid3_lib::Bin2Dec($exponentstring) - 16383); |
| 196 | + $fraction = $isnormalized + getid3_lib::DecimalBinary2Float($fractionstring); |
| 197 | + $floatvalue = $exponent * $fraction; |
| 198 | + if ($signbit == '1') { |
| 199 | + $floatvalue *= -1; |
| 200 | + } |
| 201 | + return $floatvalue; |
| 202 | + break; |
| 203 | + |
| 204 | + default: |
| 205 | + return false; |
| 206 | + break; |
| 207 | + } |
| 208 | + $exponentstring = substr($bitword, 1, $exponentbits); |
| 209 | + $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); |
| 210 | + $exponent = getid3_lib::Bin2Dec($exponentstring); |
| 211 | + $fraction = getid3_lib::Bin2Dec($fractionstring); |
| 212 | + |
| 213 | + if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { |
| 214 | + // Not a Number |
| 215 | + $floatvalue = false; |
| 216 | + } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { |
| 217 | + if ($signbit == '1') { |
| 218 | + $floatvalue = '-infinity'; |
| 219 | + } else { |
| 220 | + $floatvalue = '+infinity'; |
| 221 | + } |
| 222 | + } elseif (($exponent == 0) && ($fraction == 0)) { |
| 223 | + if ($signbit == '1') { |
| 224 | + $floatvalue = -0; |
| 225 | + } else { |
| 226 | + $floatvalue = 0; |
| 227 | + } |
| 228 | + $floatvalue = ($signbit ? 0 : -0); |
| 229 | + } elseif (($exponent == 0) && ($fraction != 0)) { |
| 230 | + // These are 'unnormalized' values |
| 231 | + $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fractionstring); |
| 232 | + if ($signbit == '1') { |
| 233 | + $floatvalue *= -1; |
| 234 | + } |
| 235 | + } elseif ($exponent != 0) { |
| 236 | + $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fractionstring)); |
| 237 | + if ($signbit == '1') { |
| 238 | + $floatvalue *= -1; |
| 239 | + } |
| 240 | + } |
| 241 | + return (float) $floatvalue; |
| 242 | + } |
| 243 | + |
| 244 | + |
| 245 | + static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { |
| 246 | + $intvalue = 0; |
| 247 | + $bytewordlen = strlen($byteword); |
| 248 | + if ($bytewordlen == 0) { |
| 249 | + return false; |
| 250 | + } |
| 251 | + for ($i = 0; $i < $bytewordlen; $i++) { |
| 252 | + if ($synchsafe) { // disregard MSB, effectively 7-bit bytes |
| 253 | + $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); |
| 254 | + } else { |
| 255 | + $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); |
| 256 | + } |
| 257 | + } |
| 258 | + if ($signed && !$synchsafe) { |
| 259 | + // synchsafe ints are not allowed to be signed |
| 260 | + switch ($bytewordlen) { |
| 261 | + case 1: |
| 262 | + case 2: |
| 263 | + case 3: |
| 264 | + case 4: |
| 265 | + $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); |
| 266 | + if ($intvalue & $signMaskBit) { |
| 267 | + $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); |
| 268 | + } |
| 269 | + break; |
| 270 | + |
| 271 | + default: |
| 272 | + die('ERROR: Cannot have signed integers larger than 32-bits ('.strlen($byteword).') in getid3_lib::BigEndian2Int()'); |
| 273 | + break; |
| 274 | + } |
| 275 | + } |
| 276 | + return getid3_lib::CastAsInt($intvalue); |
| 277 | + } |
| 278 | + |
| 279 | + |
| 280 | + static function LittleEndian2Int($byteword, $signed=false) { |
| 281 | + return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed); |
| 282 | + } |
| 283 | + |
| 284 | + |
| 285 | + static function BigEndian2Bin($byteword) { |
| 286 | + $binvalue = ''; |
| 287 | + $bytewordlen = strlen($byteword); |
| 288 | + for ($i = 0; $i < $bytewordlen; $i++) { |
| 289 | + $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT); |
| 290 | + } |
| 291 | + return $binvalue; |
| 292 | + } |
| 293 | + |
| 294 | + |
| 295 | + static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { |
| 296 | + if ($number < 0) { |
| 297 | + return false; |
| 298 | + } |
| 299 | + $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); |
| 300 | + $intstring = ''; |
| 301 | + if ($signed) { |
| 302 | + if ($minbytes > 4) { |
| 303 | + die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2String()'); |
| 304 | + } |
| 305 | + $number = $number & (0x80 << (8 * ($minbytes - 1))); |
| 306 | + } |
| 307 | + while ($number != 0) { |
| 308 | + $quotient = ($number / ($maskbyte + 1)); |
| 309 | + $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; |
| 310 | + $number = floor($quotient); |
| 311 | + } |
| 312 | + return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); |
| 313 | + } |
| 314 | + |
| 315 | + |
| 316 | + static function Dec2Bin($number) { |
| 317 | + while ($number >= 256) { |
| 318 | + $bytes[] = (($number / 256) - (floor($number / 256))) * 256; |
| 319 | + $number = floor($number / 256); |
| 320 | + } |
| 321 | + $bytes[] = $number; |
| 322 | + $binstring = ''; |
| 323 | + for ($i = 0; $i < count($bytes); $i++) { |
| 324 | + $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; |
| 325 | + } |
| 326 | + return $binstring; |
| 327 | + } |
| 328 | + |
| 329 | + |
| 330 | + static function Bin2Dec($binstring, $signed=false) { |
| 331 | + $signmult = 1; |
| 332 | + if ($signed) { |
| 333 | + if ($binstring{0} == '1') { |
| 334 | + $signmult = -1; |
| 335 | + } |
| 336 | + $binstring = substr($binstring, 1); |
| 337 | + } |
| 338 | + $decvalue = 0; |
| 339 | + for ($i = 0; $i < strlen($binstring); $i++) { |
| 340 | + $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); |
| 341 | + } |
| 342 | + return getid3_lib::CastAsInt($decvalue * $signmult); |
| 343 | + } |
| 344 | + |
| 345 | + |
| 346 | + static function Bin2String($binstring) { |
| 347 | + // return 'hi' for input of '0110100001101001' |
| 348 | + $string = ''; |
| 349 | + $binstringreversed = strrev($binstring); |
| 350 | + for ($i = 0; $i < strlen($binstringreversed); $i += 8) { |
| 351 | + $string = chr(getid3_lib::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; |
| 352 | + } |
| 353 | + return $string; |
| 354 | + } |
| 355 | + |
| 356 | + |
| 357 | + static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { |
| 358 | + $intstring = ''; |
| 359 | + while ($number > 0) { |
| 360 | + if ($synchsafe) { |
| 361 | + $intstring = $intstring.chr($number & 127); |
| 362 | + $number >>= 7; |
| 363 | + } else { |
| 364 | + $intstring = $intstring.chr($number & 255); |
| 365 | + $number >>= 8; |
| 366 | + } |
| 367 | + } |
| 368 | + return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); |
| 369 | + } |
| 370 | + |
| 371 | + |
| 372 | + static function array_merge_clobber($array1, $array2) { |
| 373 | + // written by kcØhireability*com |
| 374 | + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php |
| 375 | + if (!is_array($array1) || !is_array($array2)) { |
| 376 | + return false; |
| 377 | + } |
| 378 | + $newarray = $array1; |
| 379 | + foreach ($array2 as $key => $val) { |
| 380 | + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { |
| 381 | + $newarray[$key] = getid3_lib::array_merge_clobber($newarray[$key], $val); |
| 382 | + } else { |
| 383 | + $newarray[$key] = $val; |
| 384 | + } |
| 385 | + } |
| 386 | + return $newarray; |
| 387 | + } |
| 388 | + |
| 389 | + |
| 390 | + static function array_merge_noclobber($array1, $array2) { |
| 391 | + if (!is_array($array1) || !is_array($array2)) { |
| 392 | + return false; |
| 393 | + } |
| 394 | + $newarray = $array1; |
| 395 | + foreach ($array2 as $key => $val) { |
| 396 | + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { |
| 397 | + $newarray[$key] = getid3_lib::array_merge_noclobber($newarray[$key], $val); |
| 398 | + } elseif (!isset($newarray[$key])) { |
| 399 | + $newarray[$key] = $val; |
| 400 | + } |
| 401 | + } |
| 402 | + return $newarray; |
| 403 | + } |
| 404 | + |
| 405 | + |
| 406 | + static function fileextension($filename, $numextensions=1) { |
| 407 | + if (strstr($filename, '.')) { |
| 408 | + $reversedfilename = strrev($filename); |
| 409 | + $offset = 0; |
| 410 | + for ($i = 0; $i < $numextensions; $i++) { |
| 411 | + $offset = strpos($reversedfilename, '.', $offset + 1); |
| 412 | + if ($offset === false) { |
| 413 | + return ''; |
| 414 | + } |
| 415 | + } |
| 416 | + return strrev(substr($reversedfilename, 0, $offset)); |
| 417 | + } |
| 418 | + return ''; |
| 419 | + } |
| 420 | + |
| 421 | + |
| 422 | + static function PlaytimeString($playtimeseconds) { |
| 423 | + $sign = (($playtimeseconds < 0) ? '-' : ''); |
| 424 | + $playtimeseconds = abs($playtimeseconds); |
| 425 | + $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60); |
| 426 | + $contentminutes = floor($playtimeseconds / 60); |
| 427 | + if ($contentseconds >= 60) { |
| 428 | + $contentseconds -= 60; |
| 429 | + $contentminutes++; |
| 430 | + } |
| 431 | + return $sign.intval($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT); |
| 432 | + } |
| 433 | + |
| 434 | + |
| 435 | + static function image_type_to_mime_type($imagetypeid) { |
| 436 | + // only available in PHP v4.3.0+ |
| 437 | + static $image_type_to_mime_type = array(); |
| 438 | + if (empty($image_type_to_mime_type)) { |
| 439 | + $image_type_to_mime_type[1] = 'image/gif'; // GIF |
| 440 | + $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG |
| 441 | + $image_type_to_mime_type[3] = 'image/png'; // PNG |
| 442 | + $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash |
| 443 | + $image_type_to_mime_type[5] = 'image/psd'; // PSD |
| 444 | + $image_type_to_mime_type[6] = 'image/bmp'; // BMP |
| 445 | + $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel) |
| 446 | + $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola) |
| 447 | + //$image_type_to_mime_type[9] = 'image/jpc'; // JPC |
| 448 | + //$image_type_to_mime_type[10] = 'image/jp2'; // JPC |
| 449 | + //$image_type_to_mime_type[11] = 'image/jpx'; // JPC |
| 450 | + //$image_type_to_mime_type[12] = 'image/jb2'; // JPC |
| 451 | + $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave |
| 452 | + $image_type_to_mime_type[14] = 'image/iff'; // IFF |
| 453 | + } |
| 454 | + return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream'); |
| 455 | + } |
| 456 | + |
| 457 | + |
| 458 | + static function DateMac2Unix($macdate) { |
| 459 | + // Macintosh timestamp: seconds since 00:00h January 1, 1904 |
| 460 | + // UNIX timestamp: seconds since 00:00h January 1, 1970 |
| 461 | + return getid3_lib::CastAsInt($macdate - 2082844800); |
| 462 | + } |
| 463 | + |
| 464 | + |
| 465 | + static function FixedPoint8_8($rawdata) { |
| 466 | + return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); |
| 467 | + } |
| 468 | + |
| 469 | + |
| 470 | + static function FixedPoint16_16($rawdata) { |
| 471 | + return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); |
| 472 | + } |
| 473 | + |
| 474 | + |
| 475 | + static function FixedPoint2_30($rawdata) { |
| 476 | + $binarystring = getid3_lib::BigEndian2Bin($rawdata); |
| 477 | + return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / 1073741824); |
| 478 | + } |
| 479 | + |
| 480 | + |
| 481 | + static function CreateDeepArray($ArrayPath, $Separator, $Value) { |
| 482 | + // assigns $Value to a nested array path: |
| 483 | + // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt') |
| 484 | + // is the same as: |
| 485 | + // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); |
| 486 | + // or |
| 487 | + // $foo['path']['to']['my'] = 'file.txt'; |
| 488 | + while ($ArrayPath && ($ArrayPath{0} == $Separator)) { |
| 489 | + $ArrayPath = substr($ArrayPath, 1); |
| 490 | + } |
| 491 | + if (($pos = strpos($ArrayPath, $Separator)) !== false) { |
| 492 | + $ReturnedArray[substr($ArrayPath, 0, $pos)] = getid3_lib::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); |
| 493 | + } else { |
| 494 | + $ReturnedArray[$ArrayPath] = $Value; |
| 495 | + } |
| 496 | + return $ReturnedArray; |
| 497 | + } |
| 498 | + |
| 499 | + static function array_max($arraydata, $returnkey=false) { |
| 500 | + $maxvalue = false; |
| 501 | + $maxkey = false; |
| 502 | + foreach ($arraydata as $key => $value) { |
| 503 | + if (!is_array($value)) { |
| 504 | + if ($value > $maxvalue) { |
| 505 | + $maxvalue = $value; |
| 506 | + $maxkey = $key; |
| 507 | + } |
| 508 | + } |
| 509 | + } |
| 510 | + return ($returnkey ? $maxkey : $maxvalue); |
| 511 | + } |
| 512 | + |
| 513 | + static function array_min($arraydata, $returnkey=false) { |
| 514 | + $minvalue = false; |
| 515 | + $minkey = false; |
| 516 | + foreach ($arraydata as $key => $value) { |
| 517 | + if (!is_array($value)) { |
| 518 | + if ($value > $minvalue) { |
| 519 | + $minvalue = $value; |
| 520 | + $minkey = $key; |
| 521 | + } |
| 522 | + } |
| 523 | + } |
| 524 | + return ($returnkey ? $minkey : $minvalue); |
| 525 | + } |
| 526 | + |
| 527 | + |
| 528 | + static function md5_file($file) { |
| 529 | + |
| 530 | + // md5_file() exists in PHP 4.2.0+. |
| 531 | + if (function_exists('md5_file')) { |
| 532 | + return md5_file($file); |
| 533 | + } |
| 534 | + |
| 535 | + if (GETID3_OS_ISWINDOWS) { |
| 536 | + |
| 537 | + $RequiredFiles = array('cygwin1.dll', 'md5sum.exe'); |
| 538 | + foreach ($RequiredFiles as $required_file) { |
| 539 | + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { |
| 540 | + die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::md5_file() to function under Windows in PHP < v4.2.0'); |
| 541 | + } |
| 542 | + } |
| 543 | + $commandline = GETID3_HELPERAPPSDIR.'md5sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; |
| 544 | + if (preg_match("#^[\\]?([0-9a-f]{32})#", strtolower(`$commandline`), $r)) { |
| 545 | + return $r[1]; |
| 546 | + } |
| 547 | + |
| 548 | + } else { |
| 549 | + |
| 550 | + // The following works under UNIX only |
| 551 | + $file = str_replace('`', '\\`', $file); |
| 552 | + if (preg_match("#^([0-9a-f]{32})[ \t\n\r]#i", `md5sum "$file"`, $r)) { |
| 553 | + return $r[1]; |
| 554 | + } |
| 555 | + |
| 556 | + } |
| 557 | + return false; |
| 558 | + } |
| 559 | + |
| 560 | + |
| 561 | + static function sha1_file($file) { |
| 562 | + |
| 563 | + // sha1_file() exists in PHP 4.3.0+. |
| 564 | + if (function_exists('sha1_file')) { |
| 565 | + return sha1_file($file); |
| 566 | + } |
| 567 | + |
| 568 | + $file = str_replace('`', '\\`', $file); |
| 569 | + |
| 570 | + if (GETID3_OS_ISWINDOWS) { |
| 571 | + |
| 572 | + $RequiredFiles = array('cygwin1.dll', 'sha1sum.exe'); |
| 573 | + foreach ($RequiredFiles as $required_file) { |
| 574 | + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { |
| 575 | + die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::sha1_file() to function under Windows in PHP < v4.3.0'); |
| 576 | + } |
| 577 | + } |
| 578 | + $commandline = GETID3_HELPERAPPSDIR.'sha1sum.exe "'.str_replace('/', DIRECTORY_SEPARATOR, $file).'"'; |
| 579 | + if (preg_match("#^sha1=([0-9a-f]{40})#", strtolower(`$commandline`), $r)) { |
| 580 | + return $r[1]; |
| 581 | + } |
| 582 | + |
| 583 | + } else { |
| 584 | + |
| 585 | + $commandline = 'sha1sum '.escapeshellarg($file).''; |
| 586 | + if (preg_match("#^([0-9a-f]{40})[ \t\n\r]#", strtolower(`$commandline`), $r)) { |
| 587 | + return $r[1]; |
| 588 | + } |
| 589 | + |
| 590 | + } |
| 591 | + |
| 592 | + return false; |
| 593 | + } |
| 594 | + |
| 595 | + |
| 596 | + // Allan Hansen <ahØartemis*dk> |
| 597 | + // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position |
| 598 | + static function hash_data($file, $offset, $end, $algorithm) { |
| 599 | + if ($end >= pow(2, 31)) { |
| 600 | + return false; |
| 601 | + } |
| 602 | + |
| 603 | + switch ($algorithm) { |
| 604 | + case 'md5': |
| 605 | + $hash_function = 'md5_file'; |
| 606 | + $unix_call = 'md5sum'; |
| 607 | + $windows_call = 'md5sum.exe'; |
| 608 | + $hash_length = 32; |
| 609 | + break; |
| 610 | + |
| 611 | + case 'sha1': |
| 612 | + $hash_function = 'sha1_file'; |
| 613 | + $unix_call = 'sha1sum'; |
| 614 | + $windows_call = 'sha1sum.exe'; |
| 615 | + $hash_length = 40; |
| 616 | + break; |
| 617 | + |
| 618 | + default: |
| 619 | + die('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); |
| 620 | + break; |
| 621 | + } |
| 622 | + $size = $end - $offset; |
| 623 | + while (true) { |
| 624 | + if (GETID3_OS_ISWINDOWS) { |
| 625 | + |
| 626 | + // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data |
| 627 | + // Fall back to create-temp-file method: |
| 628 | + if ($algorithm == 'sha1') { |
| 629 | + break; |
| 630 | + } |
| 631 | + |
| 632 | + $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call); |
| 633 | + foreach ($RequiredFiles as $required_file) { |
| 634 | + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { |
| 635 | + // helper apps not available - fall back to old method |
| 636 | + break; |
| 637 | + } |
| 638 | + } |
| 639 | + $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' "'.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).'" | '; |
| 640 | + $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; |
| 641 | + $commandline .= GETID3_HELPERAPPSDIR.$windows_call; |
| 642 | + |
| 643 | + } else { |
| 644 | + |
| 645 | + $commandline = 'head -c'.$end.' '.escapeshellarg($file).' | '; |
| 646 | + $commandline .= 'tail -c'.$size.' | '; |
| 647 | + $commandline .= $unix_call; |
| 648 | + |
| 649 | + } |
| 650 | + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { |
| 651 | + $ThisFileInfo['warning'][] = 'PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'; |
| 652 | + break; |
| 653 | + } |
| 654 | + return substr(`$commandline`, 0, $hash_length); |
| 655 | + } |
| 656 | + |
| 657 | + // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir |
| 658 | + if (($data_filename = tempnam(GETID3_TEMP_DIR, 'getID3')) === false) { |
| 659 | + // can't find anywhere to create a temp file, just die |
| 660 | + return false; |
| 661 | + } |
| 662 | + |
| 663 | + // Init |
| 664 | + $result = false; |
| 665 | + |
| 666 | + // copy parts of file |
| 667 | + ob_start(); |
| 668 | + if ($fp = fopen($file, 'rb')) { |
| 669 | + ob_end_clean(); |
| 670 | + ob_start(); |
| 671 | + if ($fp_data = fopen($data_filename, 'wb')) { |
| 672 | + fseek($fp, $offset, SEEK_SET); |
| 673 | + $byteslefttowrite = $end - $offset; |
| 674 | + while (($byteslefttowrite > 0) && ($buffer = fread($fp, GETID3_FREAD_BUFFER_SIZE))) { |
| 675 | + $byteswritten = fwrite($fp_data, $buffer, $byteslefttowrite); |
| 676 | + $byteslefttowrite -= $byteswritten; |
| 677 | + } |
| 678 | + fclose($fp_data); |
| 679 | + $result = getid3_lib::$hash_function($data_filename); |
| 680 | + } else { |
| 681 | + $errormessage = ob_get_contents(); |
| 682 | + ob_end_clean(); |
| 683 | + } |
| 684 | + fclose($fp); |
| 685 | + } else { |
| 686 | + $errormessage = ob_get_contents(); |
| 687 | + ob_end_clean(); |
| 688 | + } |
| 689 | + unlink($data_filename); |
| 690 | + return $result; |
| 691 | + } |
| 692 | + |
| 693 | + |
| 694 | + static function iconv_fallback_int_utf8($charval) { |
| 695 | + if ($charval < 128) { |
| 696 | + // 0bbbbbbb |
| 697 | + $newcharstring = chr($charval); |
| 698 | + } elseif ($charval < 2048) { |
| 699 | + // 110bbbbb 10bbbbbb |
| 700 | + $newcharstring = chr(($charval >> 6) | 0xC0); |
| 701 | + $newcharstring .= chr(($charval & 0x3F) | 0x80); |
| 702 | + } elseif ($charval < 65536) { |
| 703 | + // 1110bbbb 10bbbbbb 10bbbbbb |
| 704 | + $newcharstring = chr(($charval >> 12) | 0xE0); |
| 705 | + $newcharstring .= chr(($charval >> 6) | 0xC0); |
| 706 | + $newcharstring .= chr(($charval & 0x3F) | 0x80); |
| 707 | + } else { |
| 708 | + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb |
| 709 | + $newcharstring = chr(($charval >> 18) | 0xF0); |
| 710 | + $newcharstring .= chr(($charval >> 12) | 0xC0); |
| 711 | + $newcharstring .= chr(($charval >> 6) | 0xC0); |
| 712 | + $newcharstring .= chr(($charval & 0x3F) | 0x80); |
| 713 | + } |
| 714 | + return $newcharstring; |
| 715 | + } |
| 716 | + |
| 717 | + // ISO-8859-1 => UTF-8 |
| 718 | + static function iconv_fallback_iso88591_utf8($string, $bom=false) { |
| 719 | + if (function_exists('utf8_encode')) { |
| 720 | + return utf8_encode($string); |
| 721 | + } |
| 722 | + // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) |
| 723 | + $newcharstring = ''; |
| 724 | + if ($bom) { |
| 725 | + $newcharstring .= "\xEF\xBB\xBF"; |
| 726 | + } |
| 727 | + for ($i = 0; $i < strlen($string); $i++) { |
| 728 | + $charval = ord($string{$i}); |
| 729 | + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); |
| 730 | + } |
| 731 | + return $newcharstring; |
| 732 | + } |
| 733 | + |
| 734 | + // ISO-8859-1 => UTF-16BE |
| 735 | + static function iconv_fallback_iso88591_utf16be($string, $bom=false) { |
| 736 | + $newcharstring = ''; |
| 737 | + if ($bom) { |
| 738 | + $newcharstring .= "\xFE\xFF"; |
| 739 | + } |
| 740 | + for ($i = 0; $i < strlen($string); $i++) { |
| 741 | + $newcharstring .= "\x00".$string{$i}; |
| 742 | + } |
| 743 | + return $newcharstring; |
| 744 | + } |
| 745 | + |
| 746 | + // ISO-8859-1 => UTF-16LE |
| 747 | + static function iconv_fallback_iso88591_utf16le($string, $bom=false) { |
| 748 | + $newcharstring = ''; |
| 749 | + if ($bom) { |
| 750 | + $newcharstring .= "\xFF\xFE"; |
| 751 | + } |
| 752 | + for ($i = 0; $i < strlen($string); $i++) { |
| 753 | + $newcharstring .= $string{$i}."\x00"; |
| 754 | + } |
| 755 | + return $newcharstring; |
| 756 | + } |
| 757 | + |
| 758 | + // ISO-8859-1 => UTF-16LE (BOM) |
| 759 | + static function iconv_fallback_iso88591_utf16($string) { |
| 760 | + return getid3_lib::iconv_fallback_iso88591_utf16le($string, true); |
| 761 | + } |
| 762 | + |
| 763 | + // UTF-8 => ISO-8859-1 |
| 764 | + static function iconv_fallback_utf8_iso88591($string) { |
| 765 | + if (function_exists('utf8_decode')) { |
| 766 | + return utf8_decode($string); |
| 767 | + } |
| 768 | + // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) |
| 769 | + $newcharstring = ''; |
| 770 | + $offset = 0; |
| 771 | + $stringlength = strlen($string); |
| 772 | + while ($offset < $stringlength) { |
| 773 | + if ((ord($string{$offset}) | 0x07) == 0xF7) { |
| 774 | + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb |
| 775 | + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & |
| 776 | + ((ord($string{($offset + 1)}) & 0x3F) << 12) & |
| 777 | + ((ord($string{($offset + 2)}) & 0x3F) << 6) & |
| 778 | + (ord($string{($offset + 3)}) & 0x3F); |
| 779 | + $offset += 4; |
| 780 | + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { |
| 781 | + // 1110bbbb 10bbbbbb 10bbbbbb |
| 782 | + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & |
| 783 | + ((ord($string{($offset + 1)}) & 0x3F) << 6) & |
| 784 | + (ord($string{($offset + 2)}) & 0x3F); |
| 785 | + $offset += 3; |
| 786 | + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { |
| 787 | + // 110bbbbb 10bbbbbb |
| 788 | + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & |
| 789 | + (ord($string{($offset + 1)}) & 0x3F); |
| 790 | + $offset += 2; |
| 791 | + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { |
| 792 | + // 0bbbbbbb |
| 793 | + $charval = ord($string{$offset}); |
| 794 | + $offset += 1; |
| 795 | + } else { |
| 796 | + // error? throw some kind of warning here? |
| 797 | + $charval = false; |
| 798 | + $offset += 1; |
| 799 | + } |
| 800 | + if ($charval !== false) { |
| 801 | + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); |
| 802 | + } |
| 803 | + } |
| 804 | + return $newcharstring; |
| 805 | + } |
| 806 | + |
| 807 | + // UTF-8 => UTF-16BE |
| 808 | + static function iconv_fallback_utf8_utf16be($string, $bom=false) { |
| 809 | + $newcharstring = ''; |
| 810 | + if ($bom) { |
| 811 | + $newcharstring .= "\xFE\xFF"; |
| 812 | + } |
| 813 | + $offset = 0; |
| 814 | + $stringlength = strlen($string); |
| 815 | + while ($offset < $stringlength) { |
| 816 | + if ((ord($string{$offset}) | 0x07) == 0xF7) { |
| 817 | + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb |
| 818 | + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & |
| 819 | + ((ord($string{($offset + 1)}) & 0x3F) << 12) & |
| 820 | + ((ord($string{($offset + 2)}) & 0x3F) << 6) & |
| 821 | + (ord($string{($offset + 3)}) & 0x3F); |
| 822 | + $offset += 4; |
| 823 | + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { |
| 824 | + // 1110bbbb 10bbbbbb 10bbbbbb |
| 825 | + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & |
| 826 | + ((ord($string{($offset + 1)}) & 0x3F) << 6) & |
| 827 | + (ord($string{($offset + 2)}) & 0x3F); |
| 828 | + $offset += 3; |
| 829 | + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { |
| 830 | + // 110bbbbb 10bbbbbb |
| 831 | + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & |
| 832 | + (ord($string{($offset + 1)}) & 0x3F); |
| 833 | + $offset += 2; |
| 834 | + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { |
| 835 | + // 0bbbbbbb |
| 836 | + $charval = ord($string{$offset}); |
| 837 | + $offset += 1; |
| 838 | + } else { |
| 839 | + // error? throw some kind of warning here? |
| 840 | + $charval = false; |
| 841 | + $offset += 1; |
| 842 | + } |
| 843 | + if ($charval !== false) { |
| 844 | + $newcharstring .= (($charval < 65536) ? getid3_lib::BigEndian2String($charval, 2) : "\x00".'?'); |
| 845 | + } |
| 846 | + } |
| 847 | + return $newcharstring; |
| 848 | + } |
| 849 | + |
| 850 | + // UTF-8 => UTF-16LE |
| 851 | + static function iconv_fallback_utf8_utf16le($string, $bom=false) { |
| 852 | + $newcharstring = ''; |
| 853 | + if ($bom) { |
| 854 | + $newcharstring .= "\xFF\xFE"; |
| 855 | + } |
| 856 | + $offset = 0; |
| 857 | + $stringlength = strlen($string); |
| 858 | + while ($offset < $stringlength) { |
| 859 | + if ((ord($string{$offset}) | 0x07) == 0xF7) { |
| 860 | + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb |
| 861 | + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & |
| 862 | + ((ord($string{($offset + 1)}) & 0x3F) << 12) & |
| 863 | + ((ord($string{($offset + 2)}) & 0x3F) << 6) & |
| 864 | + (ord($string{($offset + 3)}) & 0x3F); |
| 865 | + $offset += 4; |
| 866 | + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { |
| 867 | + // 1110bbbb 10bbbbbb 10bbbbbb |
| 868 | + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & |
| 869 | + ((ord($string{($offset + 1)}) & 0x3F) << 6) & |
| 870 | + (ord($string{($offset + 2)}) & 0x3F); |
| 871 | + $offset += 3; |
| 872 | + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { |
| 873 | + // 110bbbbb 10bbbbbb |
| 874 | + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & |
| 875 | + (ord($string{($offset + 1)}) & 0x3F); |
| 876 | + $offset += 2; |
| 877 | + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { |
| 878 | + // 0bbbbbbb |
| 879 | + $charval = ord($string{$offset}); |
| 880 | + $offset += 1; |
| 881 | + } else { |
| 882 | + // error? maybe throw some warning here? |
| 883 | + $charval = false; |
| 884 | + $offset += 1; |
| 885 | + } |
| 886 | + if ($charval !== false) { |
| 887 | + $newcharstring .= (($charval < 65536) ? getid3_lib::LittleEndian2String($charval, 2) : '?'."\x00"); |
| 888 | + } |
| 889 | + } |
| 890 | + return $newcharstring; |
| 891 | + } |
| 892 | + |
| 893 | + // UTF-8 => UTF-16LE (BOM) |
| 894 | + static function iconv_fallback_utf8_utf16($string) { |
| 895 | + return getid3_lib::iconv_fallback_utf8_utf16le($string, true); |
| 896 | + } |
| 897 | + |
| 898 | + // UTF-16BE => UTF-8 |
| 899 | + static function iconv_fallback_utf16be_utf8($string) { |
| 900 | + if (substr($string, 0, 2) == "\xFE\xFF") { |
| 901 | + // strip BOM |
| 902 | + $string = substr($string, 2); |
| 903 | + } |
| 904 | + $newcharstring = ''; |
| 905 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 906 | + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); |
| 907 | + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); |
| 908 | + } |
| 909 | + return $newcharstring; |
| 910 | + } |
| 911 | + |
| 912 | + // UTF-16LE => UTF-8 |
| 913 | + static function iconv_fallback_utf16le_utf8($string) { |
| 914 | + if (substr($string, 0, 2) == "\xFF\xFE") { |
| 915 | + // strip BOM |
| 916 | + $string = substr($string, 2); |
| 917 | + } |
| 918 | + $newcharstring = ''; |
| 919 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 920 | + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); |
| 921 | + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); |
| 922 | + } |
| 923 | + return $newcharstring; |
| 924 | + } |
| 925 | + |
| 926 | + // UTF-16BE => ISO-8859-1 |
| 927 | + static function iconv_fallback_utf16be_iso88591($string) { |
| 928 | + if (substr($string, 0, 2) == "\xFE\xFF") { |
| 929 | + // strip BOM |
| 930 | + $string = substr($string, 2); |
| 931 | + } |
| 932 | + $newcharstring = ''; |
| 933 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 934 | + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); |
| 935 | + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); |
| 936 | + } |
| 937 | + return $newcharstring; |
| 938 | + } |
| 939 | + |
| 940 | + // UTF-16LE => ISO-8859-1 |
| 941 | + static function iconv_fallback_utf16le_iso88591($string) { |
| 942 | + if (substr($string, 0, 2) == "\xFF\xFE") { |
| 943 | + // strip BOM |
| 944 | + $string = substr($string, 2); |
| 945 | + } |
| 946 | + $newcharstring = ''; |
| 947 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 948 | + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); |
| 949 | + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); |
| 950 | + } |
| 951 | + return $newcharstring; |
| 952 | + } |
| 953 | + |
| 954 | + // UTF-16 (BOM) => ISO-8859-1 |
| 955 | + static function iconv_fallback_utf16_iso88591($string) { |
| 956 | + $bom = substr($string, 0, 2); |
| 957 | + if ($bom == "\xFE\xFF") { |
| 958 | + return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2)); |
| 959 | + } elseif ($bom == "\xFF\xFE") { |
| 960 | + return getid3_lib::iconv_fallback_utf16le_iso88591(substr($string, 2)); |
| 961 | + } |
| 962 | + return $string; |
| 963 | + } |
| 964 | + |
| 965 | + // UTF-16 (BOM) => UTF-8 |
| 966 | + static function iconv_fallback_utf16_utf8($string) { |
| 967 | + $bom = substr($string, 0, 2); |
| 968 | + if ($bom == "\xFE\xFF") { |
| 969 | + return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2)); |
| 970 | + } elseif ($bom == "\xFF\xFE") { |
| 971 | + return getid3_lib::iconv_fallback_utf16le_utf8(substr($string, 2)); |
| 972 | + } |
| 973 | + return $string; |
| 974 | + } |
| 975 | + |
| 976 | + static function iconv_fallback($in_charset, $out_charset, $string) { |
| 977 | + |
| 978 | + if ($in_charset == $out_charset) { |
| 979 | + return $string; |
| 980 | + } |
| 981 | + |
| 982 | + // iconv() availble |
| 983 | + if (function_exists('iconv')) { |
| 984 | + |
| 985 | + ob_start(); |
| 986 | + if ($converted_string = iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { |
| 987 | + ob_end_clean(); |
| 988 | + switch ($out_charset) { |
| 989 | + case 'ISO-8859-1': |
| 990 | + $converted_string = rtrim($converted_string, "\x00"); |
| 991 | + break; |
| 992 | + } |
| 993 | + return $converted_string; |
| 994 | + } else { |
| 995 | + $errormessage = ob_get_contents(); |
| 996 | + ob_end_clean(); |
| 997 | + } |
| 998 | + |
| 999 | + // iconv() may sometimes fail with "illegal character in input string" error message |
| 1000 | + // and return an empty string, but returning the unconverted string is more useful |
| 1001 | + return $string; |
| 1002 | + } |
| 1003 | + |
| 1004 | + |
| 1005 | + // iconv() not available |
| 1006 | + static $ConversionFunctionList = array(); |
| 1007 | + if (empty($ConversionFunctionList)) { |
| 1008 | + $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; |
| 1009 | + $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16'; |
| 1010 | + $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be'; |
| 1011 | + $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le'; |
| 1012 | + $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591'; |
| 1013 | + $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16'; |
| 1014 | + $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be'; |
| 1015 | + $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le'; |
| 1016 | + $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591'; |
| 1017 | + $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8'; |
| 1018 | + $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591'; |
| 1019 | + $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8'; |
| 1020 | + $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591'; |
| 1021 | + $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8'; |
| 1022 | + } |
| 1023 | + if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { |
| 1024 | + $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; |
| 1025 | + return getid3_lib::$ConversionFunction($string); |
| 1026 | + } |
| 1027 | + die('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); |
| 1028 | + } |
| 1029 | + |
| 1030 | + |
| 1031 | + static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { |
| 1032 | + $HTMLstring = ''; |
| 1033 | + |
| 1034 | + switch ($charset) { |
| 1035 | + case 'ISO-8859-1': |
| 1036 | + case 'ISO8859-1': |
| 1037 | + case 'ISO-8859-15': |
| 1038 | + case 'ISO8859-15': |
| 1039 | + case 'cp866': |
| 1040 | + case 'ibm866': |
| 1041 | + case '866': |
| 1042 | + case 'cp1251': |
| 1043 | + case 'Windows-1251': |
| 1044 | + case 'win-1251': |
| 1045 | + case '1251': |
| 1046 | + case 'cp1252': |
| 1047 | + case 'Windows-1252': |
| 1048 | + case '1252': |
| 1049 | + case 'KOI8-R': |
| 1050 | + case 'koi8-ru': |
| 1051 | + case 'koi8r': |
| 1052 | + case 'BIG5': |
| 1053 | + case '950': |
| 1054 | + case 'GB2312': |
| 1055 | + case '936': |
| 1056 | + case 'BIG5-HKSCS': |
| 1057 | + case 'Shift_JIS': |
| 1058 | + case 'SJIS': |
| 1059 | + case '932': |
| 1060 | + case 'EUC-JP': |
| 1061 | + case 'EUCJP': |
| 1062 | + $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); |
| 1063 | + break; |
| 1064 | + |
| 1065 | + case 'UTF-8': |
| 1066 | + $strlen = strlen($string); |
| 1067 | + for ($i = 0; $i < $strlen; $i++) { |
| 1068 | + $char_ord_val = ord($string{$i}); |
| 1069 | + $charval = 0; |
| 1070 | + if ($char_ord_val < 0x80) { |
| 1071 | + $charval = $char_ord_val; |
| 1072 | + } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) { |
| 1073 | + $charval = (($char_ord_val & 0x07) << 18); |
| 1074 | + $charval += ((ord($string{++$i}) & 0x3F) << 12); |
| 1075 | + $charval += ((ord($string{++$i}) & 0x3F) << 6); |
| 1076 | + $charval += (ord($string{++$i}) & 0x3F); |
| 1077 | + } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) { |
| 1078 | + $charval = (($char_ord_val & 0x0F) << 12); |
| 1079 | + $charval += ((ord($string{++$i}) & 0x3F) << 6); |
| 1080 | + $charval += (ord($string{++$i}) & 0x3F); |
| 1081 | + } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) { |
| 1082 | + $charval = (($char_ord_val & 0x1F) << 6); |
| 1083 | + $charval += (ord($string{++$i}) & 0x3F); |
| 1084 | + } |
| 1085 | + if (($charval >= 32) && ($charval <= 127)) { |
| 1086 | + $HTMLstring .= htmlentities(chr($charval)); |
| 1087 | + } else { |
| 1088 | + $HTMLstring .= '&#'.$charval.';'; |
| 1089 | + } |
| 1090 | + } |
| 1091 | + break; |
| 1092 | + |
| 1093 | + case 'UTF-16LE': |
| 1094 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 1095 | + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); |
| 1096 | + if (($charval >= 32) && ($charval <= 127)) { |
| 1097 | + $HTMLstring .= chr($charval); |
| 1098 | + } else { |
| 1099 | + $HTMLstring .= '&#'.$charval.';'; |
| 1100 | + } |
| 1101 | + } |
| 1102 | + break; |
| 1103 | + |
| 1104 | + case 'UTF-16BE': |
| 1105 | + for ($i = 0; $i < strlen($string); $i += 2) { |
| 1106 | + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); |
| 1107 | + if (($charval >= 32) && ($charval <= 127)) { |
| 1108 | + $HTMLstring .= chr($charval); |
| 1109 | + } else { |
| 1110 | + $HTMLstring .= '&#'.$charval.';'; |
| 1111 | + } |
| 1112 | + } |
| 1113 | + break; |
| 1114 | + |
| 1115 | + default: |
| 1116 | + $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()'; |
| 1117 | + break; |
| 1118 | + } |
| 1119 | + return $HTMLstring; |
| 1120 | + } |
| 1121 | + |
| 1122 | + |
| 1123 | + |
| 1124 | + static function RGADnameLookup($namecode) { |
| 1125 | + static $RGADname = array(); |
| 1126 | + if (empty($RGADname)) { |
| 1127 | + $RGADname[0] = 'not set'; |
| 1128 | + $RGADname[1] = 'Track Gain Adjustment'; |
| 1129 | + $RGADname[2] = 'Album Gain Adjustment'; |
| 1130 | + } |
| 1131 | + |
| 1132 | + return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); |
| 1133 | + } |
| 1134 | + |
| 1135 | + |
| 1136 | + static function RGADoriginatorLookup($originatorcode) { |
| 1137 | + static $RGADoriginator = array(); |
| 1138 | + if (empty($RGADoriginator)) { |
| 1139 | + $RGADoriginator[0] = 'unspecified'; |
| 1140 | + $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer'; |
| 1141 | + $RGADoriginator[2] = 'set by user'; |
| 1142 | + $RGADoriginator[3] = 'determined automatically'; |
| 1143 | + } |
| 1144 | + |
| 1145 | + return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); |
| 1146 | + } |
| 1147 | + |
| 1148 | + |
| 1149 | + static function RGADadjustmentLookup($rawadjustment, $signbit) { |
| 1150 | + $adjustment = $rawadjustment / 10; |
| 1151 | + if ($signbit == 1) { |
| 1152 | + $adjustment *= -1; |
| 1153 | + } |
| 1154 | + return (float) $adjustment; |
| 1155 | + } |
| 1156 | + |
| 1157 | + |
| 1158 | + static function RGADgainString($namecode, $originatorcode, $replaygain) { |
| 1159 | + if ($replaygain < 0) { |
| 1160 | + $signbit = '1'; |
| 1161 | + } else { |
| 1162 | + $signbit = '0'; |
| 1163 | + } |
| 1164 | + $storedreplaygain = intval(round($replaygain * 10)); |
| 1165 | + $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT); |
| 1166 | + $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT); |
| 1167 | + $gainstring .= $signbit; |
| 1168 | + $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT); |
| 1169 | + |
| 1170 | + return $gainstring; |
| 1171 | + } |
| 1172 | + |
| 1173 | + static function RGADamplitude2dB($amplitude) { |
| 1174 | + return 20 * log10($amplitude); |
| 1175 | + } |
| 1176 | + |
| 1177 | + |
| 1178 | + static function GetDataImageSize($imgData, &$imageinfo) { |
| 1179 | + $GetDataImageSize = false; |
| 1180 | + if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { |
| 1181 | + ob_start(); |
| 1182 | + if ($tmp = fopen($tempfilename, 'wb')) { |
| 1183 | + ob_end_clean(); |
| 1184 | + fwrite($tmp, $imgData); |
| 1185 | + fclose($tmp); |
| 1186 | + ob_start(); |
| 1187 | + $GetDataImageSize = GetImageSize($tempfilename, $imageinfo); |
| 1188 | + $errormessage = ob_get_contents(); |
| 1189 | + ob_end_clean(); |
| 1190 | + } else { |
| 1191 | + $errormessage = ob_get_contents(); |
| 1192 | + ob_end_clean(); |
| 1193 | + } |
| 1194 | + unlink($tempfilename); |
| 1195 | + } |
| 1196 | + return $GetDataImageSize; |
| 1197 | + } |
| 1198 | + |
| 1199 | + static function ImageTypesLookup($imagetypeid) { |
| 1200 | + static $ImageTypesLookup = array(); |
| 1201 | + if (empty($ImageTypesLookup)) { |
| 1202 | + $ImageTypesLookup[1] = 'gif'; |
| 1203 | + $ImageTypesLookup[2] = 'jpeg'; |
| 1204 | + $ImageTypesLookup[3] = 'png'; |
| 1205 | + $ImageTypesLookup[4] = 'swf'; |
| 1206 | + $ImageTypesLookup[5] = 'psd'; |
| 1207 | + $ImageTypesLookup[6] = 'bmp'; |
| 1208 | + $ImageTypesLookup[7] = 'tiff (little-endian)'; |
| 1209 | + $ImageTypesLookup[8] = 'tiff (big-endian)'; |
| 1210 | + $ImageTypesLookup[9] = 'jpc'; |
| 1211 | + $ImageTypesLookup[10] = 'jp2'; |
| 1212 | + $ImageTypesLookup[11] = 'jpx'; |
| 1213 | + $ImageTypesLookup[12] = 'jb2'; |
| 1214 | + $ImageTypesLookup[13] = 'swc'; |
| 1215 | + $ImageTypesLookup[14] = 'iff'; |
| 1216 | + } |
| 1217 | + return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); |
| 1218 | + } |
| 1219 | + |
| 1220 | + static function CopyTagsToComments(&$ThisFileInfo) { |
| 1221 | + |
| 1222 | + // Copy all entries from ['tags'] into common ['comments'] |
| 1223 | + if (!empty($ThisFileInfo['tags'])) { |
| 1224 | + foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { |
| 1225 | + foreach ($tagarray as $tagname => $tagdata) { |
| 1226 | + foreach ($tagdata as $key => $value) { |
| 1227 | + if (!empty($value)) { |
| 1228 | + if (empty($ThisFileInfo['comments'][$tagname])) { |
| 1229 | + |
| 1230 | + // fall through and append value |
| 1231 | + |
| 1232 | + } elseif ($tagtype == 'id3v1') { |
| 1233 | + |
| 1234 | + $newvaluelength = strlen(trim($value)); |
| 1235 | + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { |
| 1236 | + $oldvaluelength = strlen(trim($existingvalue)); |
| 1237 | + if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) { |
| 1238 | + // new value is identical but shorter-than (or equal-length to) one already in comments - skip |
| 1239 | + break 2; |
| 1240 | + } |
| 1241 | + } |
| 1242 | + |
| 1243 | + } else { |
| 1244 | + |
| 1245 | + $newvaluelength = strlen(trim($value)); |
| 1246 | + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { |
| 1247 | + $oldvaluelength = strlen(trim($existingvalue)); |
| 1248 | + if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { |
| 1249 | + $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); |
| 1250 | + break 2; |
| 1251 | + } |
| 1252 | + } |
| 1253 | + |
| 1254 | + } |
| 1255 | + if (empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { |
| 1256 | + $ThisFileInfo['comments'][$tagname][] = trim($value); |
| 1257 | + } |
| 1258 | + } |
| 1259 | + } |
| 1260 | + } |
| 1261 | + } |
| 1262 | + |
| 1263 | + // Copy to ['comments_html'] |
| 1264 | + foreach ($ThisFileInfo['comments'] as $field => $values) { |
| 1265 | + foreach ($values as $index => $value) { |
| 1266 | + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); |
| 1267 | + } |
| 1268 | + } |
| 1269 | + } |
| 1270 | + } |
| 1271 | + |
| 1272 | + |
| 1273 | + static function EmbeddedLookup($key, $begin, $end, $file, $name) { |
| 1274 | + |
| 1275 | + // Cached |
| 1276 | + static $cache; |
| 1277 | + if (isset($cache[$file][$name])) { |
| 1278 | + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); |
| 1279 | + } |
| 1280 | + |
| 1281 | + // Init |
| 1282 | + $keylength = strlen($key); |
| 1283 | + $line_count = $end - $begin - 7; |
| 1284 | + |
| 1285 | + // Open php file |
| 1286 | + $fp = fopen($file, 'r'); |
| 1287 | + |
| 1288 | + // Discard $begin lines |
| 1289 | + for ($i = 0; $i < ($begin + 3); $i++) { |
| 1290 | + fgets($fp, 1024); |
| 1291 | + } |
| 1292 | + |
| 1293 | + // Loop thru line |
| 1294 | + while (0 < $line_count--) { |
| 1295 | + |
| 1296 | + // Read line |
| 1297 | + $line = ltrim(fgets($fp, 1024), "\t "); |
| 1298 | + |
| 1299 | + // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key |
| 1300 | + //$keycheck = substr($line, 0, $keylength); |
| 1301 | + //if ($key == $keycheck) { |
| 1302 | + // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1); |
| 1303 | + // break; |
| 1304 | + //} |
| 1305 | + |
| 1306 | + // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key |
| 1307 | + //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); |
| 1308 | + $explodedLine = explode("\t", $line, 2); |
| 1309 | + $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); |
| 1310 | + $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); |
| 1311 | + $cache[$file][$name][$ThisKey] = trim($ThisValue); |
| 1312 | + } |
| 1313 | + |
| 1314 | + // Close and return |
| 1315 | + fclose($fp); |
| 1316 | + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); |
| 1317 | + } |
| 1318 | + |
| 1319 | + static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { |
| 1320 | + global $GETID3_ERRORARRAY; |
| 1321 | + |
| 1322 | + if (file_exists($filename)) { |
| 1323 | + if (include_once($filename)) { |
| 1324 | + return true; |
| 1325 | + } else { |
| 1326 | + $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; |
| 1327 | + } |
| 1328 | + } else { |
| 1329 | + $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; |
| 1330 | + } |
| 1331 | + if ($DieOnFailure) { |
| 1332 | + die($diemessage); |
| 1333 | + } else { |
| 1334 | + $GETID3_ERRORARRAY[] = $diemessage; |
| 1335 | + } |
| 1336 | + return false; |
| 1337 | + } |
| 1338 | + |
| 1339 | +} |
| 1340 | + |
| 1341 | +?> |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/readme.txt |
— | — | @@ -0,0 +1,574 @@ |
| 2 | +///////////////////////////////////////////////////////////////// |
| 3 | +/// getID3() by James Heinrich <info@getid3.org> // |
| 4 | +// available at http://getid3.sourceforge.net // |
| 5 | +// or http://www.getid3.org // |
| 6 | +///////////////////////////////////////////////////////////////// |
| 7 | +// // |
| 8 | +// changelog.txt - part of getID3() // |
| 9 | +// See readme.txt for more details // |
| 10 | +// /// |
| 11 | +///////////////////////////////////////////////////////////////// |
| 12 | + |
| 13 | + This code is released under the GNU GPL: |
| 14 | + http://www.gnu.org/copyleft/gpl.html |
| 15 | + |
| 16 | + +---------------------------------------------+ |
| 17 | + | If you do use this code somewhere, send me | |
| 18 | + | an email and tell me how/where you used it. | |
| 19 | + | | |
| 20 | + | If you want to donate, there is a link on | |
| 21 | + | http://www.getid3.org for PayPal donations. | |
| 22 | + +---------------------------------------------+ |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +Quick Start |
| 27 | +=========================================================================== |
| 28 | + |
| 29 | +Q: How can I check that getID3() works on my server/files? |
| 30 | +A: Unzip getID3() to a directory, then access /demos/demo.browse.php |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | +Support |
| 35 | +=========================================================================== |
| 36 | + |
| 37 | +Q: I have a question, or I found a bug. What do I do? |
| 38 | +A: The preferred method of support requests and/or bug reports is the |
| 39 | + forum at http://support.getid3.org/ |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +Sourceforge Notification |
| 44 | +=========================================================================== |
| 45 | + |
| 46 | +It's highly recommended that you sign up for notification from |
| 47 | +Sourceforge for when new versions are released. Please visit: |
| 48 | +http://sourceforge.net/project/showfiles.php?group_id=55859 |
| 49 | +and click the little "monitor package" icon/link. If you're |
| 50 | +previously signed up for the mailing list, be aware that it has |
| 51 | +been discontinued, only the automated Sourceforge notification |
| 52 | +will be used from now on. |
| 53 | + |
| 54 | + |
| 55 | + |
| 56 | +What does getID3() do? |
| 57 | +=========================================================================== |
| 58 | + |
| 59 | +Reads & parses (to varying degrees): |
| 60 | + � tags: |
| 61 | + * APE (v1 and v2) |
| 62 | + * ID3v1 (& ID3v1.1) |
| 63 | + * ID3v2 (v2.4, v2.3, v2.2) |
| 64 | + * Lyrics3 (v1 & v2) |
| 65 | + |
| 66 | + � audio-lossy: |
| 67 | + * MP3/MP2/MP1 |
| 68 | + * MPC / Musepack |
| 69 | + * Ogg (Vorbis, OggFLAC, Speex) |
| 70 | + * AC3 |
| 71 | + * DTS |
| 72 | + * RealAudio |
| 73 | + * Speex |
| 74 | + * DSS |
| 75 | + * VQF |
| 76 | + |
| 77 | + � audio-lossless: |
| 78 | + * AIFF |
| 79 | + * AU |
| 80 | + * Bonk |
| 81 | + * CD-audio (*.cda) |
| 82 | + * FLAC |
| 83 | + * LA (Lossless Audio) |
| 84 | + * LiteWave |
| 85 | + * LPAC |
| 86 | + * MIDI |
| 87 | + * Monkey's Audio |
| 88 | + * OptimFROG |
| 89 | + * RKAU |
| 90 | + * Shorten |
| 91 | + * TTA |
| 92 | + * VOC |
| 93 | + * WAV (RIFF) |
| 94 | + * WavPack |
| 95 | + |
| 96 | + � audio-video: |
| 97 | + * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV) |
| 98 | + * AVI (RIFF) |
| 99 | + * Flash |
| 100 | + * Matroska (MKV) |
| 101 | + * MPEG-1 / MPEG-2 |
| 102 | + * NSV (Nullsoft Streaming Video) |
| 103 | + * Quicktime |
| 104 | + * RealVideo |
| 105 | + |
| 106 | + � still image: |
| 107 | + * BMP |
| 108 | + * GIF |
| 109 | + * JPEG |
| 110 | + * PNG |
| 111 | + * TIFF |
| 112 | + * SWF (Flash) |
| 113 | + * PhotoCD |
| 114 | + |
| 115 | + � data: |
| 116 | + * ISO-9660 CD-ROM image (directory structure) |
| 117 | + * SZIP (limited support) |
| 118 | + * ZIP (directory structure) |
| 119 | + * TAR |
| 120 | + * CUE |
| 121 | + |
| 122 | + |
| 123 | +Writes: |
| 124 | + * ID3v1 (& ID3v1.1) |
| 125 | + * ID3v2 (v2.3 & v2.4) |
| 126 | + * VorbisComment on OggVorbis |
| 127 | + * VorbisComment on FLAC (not OggFLAC) |
| 128 | + * APE v2 |
| 129 | + * Lyrics3 (delete only) |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +Requirements |
| 134 | +=========================================================================== |
| 135 | + |
| 136 | +* PHP 4.2.0 (or higher) for getID3() 1.7.x (and earlier) |
| 137 | +* PHP 5.3.0 (or higher) for getID3() 1.8.x (and up) |
| 138 | +* PHP 5.0.0 (or higher) for getID3() 2.0.x (and up) |
| 139 | +* at least 4MB memory for PHP. 8MB is highly recommended. |
| 140 | + 12MB is required with all modules loaded. |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | +Usage |
| 145 | +=========================================================================== |
| 146 | + |
| 147 | +See /demos/demo.basic.php for a very basic use of getID3() with no |
| 148 | +fancy output, just scanning one file. |
| 149 | + |
| 150 | +See structure.txt for the returned data structure. |
| 151 | + |
| 152 | +*> For an example of a complete directory-browsing, <* |
| 153 | +*> file-scanning implementation of getID3(), please run <* |
| 154 | +*> /demos/demo.browse.php <* |
| 155 | + |
| 156 | +See /demos/demo.mysql.php for a sample recursive scanning code that |
| 157 | +scans every file in a given directory, and all sub-directories, stores |
| 158 | +the results in a database and allows various analysis / maintenance |
| 159 | +operations |
| 160 | + |
| 161 | +To analyze remote files over HTTP or FTP you need to copy the file |
| 162 | +locally first before running getID3(). Your code would look something |
| 163 | +like this: |
| 164 | + |
| 165 | +// Copy remote file locally to scan with getID3() |
| 166 | +$remotefilename = 'http://www.example.com/filename.mp3'; |
| 167 | +if ($fp_remote = fopen($remotefilename, 'rb')) { |
| 168 | + $localtempfilename = tempnam('/tmp', 'getID3'); |
| 169 | + if ($fp_local = fopen($localtempfilename, 'wb')) { |
| 170 | + while ($buffer = fread($fp_remote, 8192)) { |
| 171 | + fwrite($fp_local, $buffer); |
| 172 | + } |
| 173 | + fclose($fp_local); |
| 174 | + |
| 175 | + // Initialize getID3 engine |
| 176 | + $getID3 = new getID3; |
| 177 | + |
| 178 | + $ThisFileInfo = $getID3->analyze($filename); |
| 179 | + |
| 180 | + // Delete temporary file |
| 181 | + unlink($localtempfilename); |
| 182 | + } |
| 183 | + fclose($fp_remote); |
| 184 | +} |
| 185 | + |
| 186 | + |
| 187 | +See /demos/demo.write.php for how to write tags. |
| 188 | + |
| 189 | + |
| 190 | + |
| 191 | +What does the returned data structure look like? |
| 192 | +=========================================================================== |
| 193 | + |
| 194 | +See structure.txt |
| 195 | + |
| 196 | +It is recommended that you look at the output of |
| 197 | +/demos/demo.browse.php scanning the file(s) you're interested in to |
| 198 | +confirm what data is actually returned for any particular filetype in |
| 199 | +general, and your files in particular, as the actual data returned |
| 200 | +may vary considerably depending on what information is available in |
| 201 | +the file itself. |
| 202 | + |
| 203 | + |
| 204 | + |
| 205 | +Notes |
| 206 | +=========================================================================== |
| 207 | + |
| 208 | +getID3() 1.x: |
| 209 | +If the format parser encounters a critical problem, it will return |
| 210 | +something in $fileinfo['error'], describing the encountered error. If |
| 211 | +a less critical error or notice is generated it will appear in |
| 212 | +$fileinfo['warning']. Both keys may contain more than one warning or |
| 213 | +error. If something is returned in ['error'] then the file was not |
| 214 | +correctly parsed and returned data may or may not be correct and/or |
| 215 | +complete. If something is returned in ['warning'] (and not ['error']) |
| 216 | +then the data that is returned is OK - usually getID3() is reporting |
| 217 | +errors in the file that have been worked around due to known bugs in |
| 218 | +other programs. Some warnings may indicate that the data that is |
| 219 | +returned is OK but that some data could not be extracted due to |
| 220 | +errors in the file. |
| 221 | + |
| 222 | +getID3() 2.x: |
| 223 | +See above except errors are thrown (so you will only get one error). |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +Disclaimer |
| 228 | +=========================================================================== |
| 229 | + |
| 230 | +getID3() has been tested on many systems, on many types of files, |
| 231 | +under many operating systems, and is generally believe to be stable |
| 232 | +and safe. That being said, there is still the chance there is an |
| 233 | +undiscovered and/or unfixed bug that may potentially corrupt your |
| 234 | +file, especially within the writing functions. By using getID3() you |
| 235 | +agree that it's not my fault if any of your files are corrupted. |
| 236 | +In fact, I'm not liable for anything :) |
| 237 | + |
| 238 | + |
| 239 | + |
| 240 | +License |
| 241 | +=========================================================================== |
| 242 | + |
| 243 | +GNU General Public License - see license.txt |
| 244 | + |
| 245 | +This program is free software; you can redistribute it and/or |
| 246 | +modify it under the terms of the GNU General Public License |
| 247 | +as published by the Free Software Foundation; either version 2 |
| 248 | +of the License, or (at your option) any later version. |
| 249 | + |
| 250 | +This program is distributed in the hope that it will be useful, |
| 251 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 252 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 253 | +GNU General Public License for more details. |
| 254 | + |
| 255 | +You should have received a copy of the GNU General Public License |
| 256 | +along with this program; if not, write to: |
| 257 | +Free Software Foundation, Inc. |
| 258 | +59 Temple Place - Suite 330 |
| 259 | +Boston, MA 02111-1307, USA. |
| 260 | + |
| 261 | +FAQ: |
| 262 | +Q: Can I use getID3() in my program? Do I need a commercial license? |
| 263 | +A: You're generally free to use getID3 however you see fit. The only |
| 264 | + case in which you would require a commercial license is if you're |
| 265 | + selling your closed-source program that integrates getID3. If you |
| 266 | + sell your program including a copy of getID3, that's fine as long |
| 267 | + as you include a copy of the sourcecode when you sell it. Or you |
| 268 | + can distribute your code without getID3 and say "download it from |
| 269 | + getid3.sourceforge.net" |
| 270 | + |
| 271 | + |
| 272 | + |
| 273 | +Future Plans |
| 274 | +=========================================================================== |
| 275 | + |
| 276 | +* Writing support for Real |
| 277 | +* Better support for MP4 container format |
| 278 | +* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) |
| 279 | +* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm) |
| 280 | +* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669) |
| 281 | +* Support for ACE (thanks Vince) |
| 282 | +* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid) |
| 283 | +* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header |
| 284 | +* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding) |
| 285 | +* Warn if MP3s change version mid-stream (in full-scan mode) |
| 286 | +* check for corrupt/broken mid-file MP3 streams in histogram scan |
| 287 | +* Support for lossless-compression formats |
| 288 | + (http://www.firstpr.com.au/audiocomp/lossless/#Links) |
| 289 | + (http://compression.ca/act-sound.html) |
| 290 | + (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm) |
| 291 | +* Support for RIFF-INFO chunks |
| 292 | + * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html |
| 293 | + (thanks Nick Humfrey <njh�surgeradio*co*uk>) |
| 294 | + * http://abcavi.narod.ru/sof/abcavi/infotags.htm |
| 295 | + (thanks Kibi) |
| 296 | +* Better support for Bink video |
| 297 | +* http://www.hr/josip/DSP/AudioFile2.html |
| 298 | +* http://www.pcisys.net/~melanson/codecs/ |
| 299 | +* Detect mp3PRO |
| 300 | +* Support for PSD |
| 301 | +* Support for JPC |
| 302 | +* Support for JP2 |
| 303 | +* Support for JPX |
| 304 | +* Support for JB2 |
| 305 | +* Support for IFF |
| 306 | +* Support for ICO |
| 307 | +* Support for ANI |
| 308 | +* Support for EXE (comments, author, etc) (thanks p*quaedackers�planet*nl) |
| 309 | +* Support for DVD-IFO (region, subtitles, aspect ratio, etc) |
| 310 | + (thanks p*quaedackers�planet*nl) |
| 311 | +* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content |
| 312 | + (thanks n8n8�yahoo*com) |
| 313 | +* Support for a2b |
| 314 | +* Optional scan-through-frames for AVI verification |
| 315 | + (thanks rockcohen�massive-interactive*nl) |
| 316 | +* Support for TTF (thanks info�butterflyx*com) |
| 317 | +* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171) |
| 318 | +* Support for SMAF (http://smaf-yamaha.com/what/demo.html) |
| 319 | + http://www.getid3.org/phpBB3/viewtopic.php?t=182 |
| 320 | +* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195) |
| 321 | +* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195) |
| 322 | +* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2K�hotmail*com) |
| 323 | +* Parse XML data returned in Ogg comments |
| 324 | +* Parse XML data from Quicktime SMIL metafiles (klausrath�mac*com) |
| 325 | +* ID3v2 genre string creator function |
| 326 | +* More complete parsing of JPG |
| 327 | +* Support for all old-style ASF packets |
| 328 | +* ASF/WMA/WMV tag writing |
| 329 | +* Parse declared T??? ID3v2 text information frames, where appropriate |
| 330 | + (thanks Christian Fritz for the idea) |
| 331 | +* Recognize encoder: |
| 332 | + http://www.guerillasoft.com/EncSpot2/index.html |
| 333 | + http://ff123.net/identify.html |
| 334 | + http://www.hydrogenaudio.org/?act=ST&f=16&t=9414 |
| 335 | + http://www.hydrogenaudio.org/?showtopic=11785 |
| 336 | +* Support for other OS/2 bitmap structures: Bitmap Array('BA'), |
| 337 | + Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT') |
| 338 | + http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm |
| 339 | +* Support for WavPack RAW mode |
| 340 | +* ASF/WMA/WMV data packet parsing |
| 341 | +* ID3v2FrameFlagsLookupTagAlter() |
| 342 | +* ID3v2FrameFlagsLookupFileAlter() |
| 343 | +* obey ID3v2 tag alter/preserve/discard rules |
| 344 | +* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm |
| 345 | +* proper checking for LINK/LNK frame validity in ID3v2 writing |
| 346 | +* proper checking for ASPI-TLEN frame validity in ID3v2 writing |
| 347 | +* proper checking for COMR frame validity in ID3v2 writing |
| 348 | +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html |
| 349 | +* decode GEOB ID3v2 structure as encoded by RealJukebox, |
| 350 | + decode NCON ID3v2 structure as encoded by MusicMatch |
| 351 | + (probably won't happen - the formats are proprietary) |
| 352 | + |
| 353 | + |
| 354 | + |
| 355 | +Known Bugs/Issues in getID3() that may be fixed eventually |
| 356 | +=========================================================================== |
| 357 | + |
| 358 | +* Cannot determine bitrate for MPEG video with VBR video data |
| 359 | + (need documentation) |
| 360 | +* Interlace/progressive cannot be determined for MPEG video |
| 361 | + (need documentation) |
| 362 | +* MIDI playtime is sometimes inaccurate |
| 363 | +* AAC-RAW mode files cannot be identified |
| 364 | +* WavPack-RAW mode files cannot be identified |
| 365 | +* mp4 files report lots of "Unknown QuickTime atom type" |
| 366 | + (need documentation) |
| 367 | +* Encrypted ASF/WMA/WMV files warn about "unhandled GUID |
| 368 | + ASF_Content_Encryption_Object" |
| 369 | +* Bitrate split between audio and video cannot be calculated for |
| 370 | + NSV, only the total bitrate. (need documentation) |
| 371 | +* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the |
| 372 | + problem of large VorbisComments spanning multiple Ogg pages, but |
| 373 | + but only OggVorbis files can be processed with vorbiscomment. |
| 374 | +* The version of "head" supplied with Mac OS 10.2.8 (maybe other |
| 375 | + versions too) does only understands a single option (-n) and |
| 376 | + therefore fails. getID3 ignores this and returns wrong md5_data. |
| 377 | + |
| 378 | + |
| 379 | + |
| 380 | +Known Bugs/Issues in getID3() that cannot be fixed |
| 381 | +-------------------------------------------------- |
| 382 | + |
| 383 | +* Files larger than 2GB cannot always be parsed fully by getID3() |
| 384 | + due to limitations in the PHP filesystem functions. |
| 385 | + NOTE: Since v1.7.8b3 there is partial support for larger-than- |
| 386 | + 2GB files, most of which will parse OK, as long as no critical |
| 387 | + data is located beyond the 2GB offset. |
| 388 | + Known will-work: |
| 389 | + * ZIP (format doesn't support files >2GB) |
| 390 | + * FLAC (current encoders don't support files >2GB) |
| 391 | + Known will-not-work: |
| 392 | + * ID3v1 tags (always located at end-of-file) |
| 393 | + * Lyrics3 tags (always located at end-of-file) |
| 394 | + * APE tags (always located at end-of-file) |
| 395 | + Maybe-will-work: |
| 396 | + * Quicktime (will work if needed metadata is before 2GB offset, |
| 397 | + that is if the file has been hinted/optimized for streaming) |
| 398 | + * RIFF.WAV (should work fine, but gives warnings about not being |
| 399 | + able to parse all chunks) |
| 400 | + * RIFF.AVI (playtime will probably be wrong, is only based on |
| 401 | + "movi" chunk that fits in the first 2GB, should issue error |
| 402 | + to show that playtime is incorrect. Other data should be mostly |
| 403 | + correct, assuming that data is constant throughout the file) |
| 404 | + |
| 405 | + |
| 406 | + |
| 407 | +Known Bugs/Issues in other programs |
| 408 | +----------------------------------- |
| 409 | + |
| 410 | +* Windows Media Player (up to v11) and iTunes (up to v10+) do |
| 411 | + not correctly handle ID3v2.3 tags with UTF-16BE+BOM |
| 412 | + encoding (they assume the data is UTF-16LE+BOM and either |
| 413 | + crash (WMP) or output Asian character set (iTunes) |
| 414 | +* Winamp (up to v2.80 at least) does not support ID3v2.4 tags, |
| 415 | + only ID3v2.3 |
| 416 | + see: http://forums.winamp.com/showthread.php?postid=387524 |
| 417 | +* Some versions of Helium2 (www.helium2.com) do not write |
| 418 | + ID3v2.4-compliant Frame Sizes, even though the tag is marked |
| 419 | + as ID3v2.4) (detected by getID3()) |
| 420 | +* MP3ext V3.3.17 places a non-compliant padding string at the end |
| 421 | + of the ID3v2 header. This is supposedly fixed in v3.4b21 but |
| 422 | + only if you manually add a registry key. This fix is not yet |
| 423 | + confirmed. (detected by getID3()) |
| 424 | +* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment |
| 425 | + strings, supposed to be in the format "NAME=value" but actually |
| 426 | + written just "value" (detected by getID3()) |
| 427 | +* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's |
| 428 | + actually ABR or VBR. |
| 429 | +* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably |
| 430 | + other versions are too) writes ID3v2.3 comment tags using a |
| 431 | + frame name 'COM ' which is not valid for ID3v2.3+ (it's an |
| 432 | + ID3v2.2-style frame name) (detected by getID3()) |
| 433 | +* MP2enc does not encode mono CBR MP2 files properly (half speed |
| 434 | + sound and double playtime) |
| 435 | +* MP2enc does not encode mono VBR MP2 files properly (actually |
| 436 | + encoded as stereo) |
| 437 | +* tooLAME does not encode mono VBR MP2 files properly (actually |
| 438 | + encoded as stereo) |
| 439 | +* AACenc encodes files in VBR mode (actually ABR) even if CBR is |
| 440 | + specified |
| 441 | +* AAC/ADIF - bitrate_mode = cbr for vbr files |
| 442 | +* LAME 3.90-3.92 prepends one frame of null data (space for the |
| 443 | + LAME/VBR header, but it never gets written) when encoding in CBR |
| 444 | + mode with the DLL |
| 445 | +* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed |
| 446 | + to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for |
| 447 | + TwinVQF v2.0 (detected by getID3()) |
| 448 | +* Ahead Nero encodes TwinVQF files 1 second shorter than they |
| 449 | + should be |
| 450 | +* AAC-ADTS files are always actually encoded VBR, even if CBR mode |
| 451 | + is specified (the CBR-mode switches on the encoder enable ABR |
| 452 | + mode, not CBR as such, but it's not possible to tell the |
| 453 | + difference between such ABR files and true VBR) |
| 454 | +* STREAMINFO.audio_signature in OggFLAC is always null. "The reason |
| 455 | + it's like that is because there is no seeking support in |
| 456 | + libOggFLAC yet, so it has no way to go back and write the |
| 457 | + computed sum after encoding. Seeking support in Ogg FLAC is the |
| 458 | + #1 item for the next release." - Josh Coalson (FLAC developer) |
| 459 | + NOTE: getID3() will calculate md5_data in a method similar to |
| 460 | + other file formats, but that value cannot be compared to the |
| 461 | + md5_data value from FLAC data in a FLAC file format. |
| 462 | +* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 & |
| 463 | + v0.4.0 - getID3() will calculate md5_data in a method similar to |
| 464 | + other file formats, but that value cannot be compared to the |
| 465 | + md5_data value from FLAC v0.5.0+ |
| 466 | +* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with |
| 467 | + a WCOM frame that has no data portion |
| 468 | +* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis |
| 469 | + files, thus making them corrupt. |
| 470 | +* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the |
| 471 | + last byte of data from an MP3 file when appending a new ID3v1 tag. |
| 472 | + (detected by getID3()) |
| 473 | +* Lossless-Audio files encoded with and without the -noseek switch |
| 474 | + do actually differ internally and therefore cannot match md5_data |
| 475 | +* iTunes has been known to append a new ID3v1 tag on the end of an |
| 476 | + existing ID3v1 tag when ID3v2 tag is also present |
| 477 | + (detected by getID3()) |
| 478 | + |
| 479 | + |
| 480 | + |
| 481 | + |
| 482 | +Reference material: |
| 483 | +=========================================================================== |
| 484 | + |
| 485 | +[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/] |
| 486 | +* http://www.id3.org/id3v2.4.0-structure.txt |
| 487 | +* http://www.id3.org/id3v2.4.0-frames.txt |
| 488 | +* http://www.id3.org/id3v2.4.0-changes.txt |
| 489 | +* http://www.id3.org/id3v2.3.0.txt |
| 490 | +* http://www.id3.org/id3v2-00.txt |
| 491 | +* http://www.id3.org/mp3frame.html |
| 492 | +* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html <mathewhendry@hotmail.com> |
| 493 | +* http://www.dv.co.yu/mpgscript/mpeghdr.htm |
| 494 | +* http://www.mp3-tech.org/programmer/frame_header.html |
| 495 | +* http://users.belgacom.net/gc247244/extra/tag.html |
| 496 | +* http://gabriel.mp3-tech.org/mp3infotag.html |
| 497 | +* http://www.id3.org/iso4217.html |
| 498 | +* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT |
| 499 | +* http://www.xiph.org/ogg/vorbis/doc/framing.html |
| 500 | +* http://www.xiph.org/ogg/vorbis/doc/v-comment.html |
| 501 | +* http://leknor.com/code/php/class.ogg.php.txt |
| 502 | +* http://www.id3.org/iso639-2.html |
| 503 | +* http://www.id3.org/lyrics3.html |
| 504 | +* http://www.id3.org/lyrics3200.html |
| 505 | +* http://www.psc.edu/general/software/packages/ieee/ieee.html |
| 506 | +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html |
| 507 | +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html |
| 508 | +* http://www.jmcgowan.com/avi.html |
| 509 | +* http://www.wotsit.org/ |
| 510 | +* http://www.herdsoft.com/ti/davincie/davp3xo2.htm |
| 511 | +* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html |
| 512 | +* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org) |
| 513 | +* http://midistudio.com/Help/GMSpecs_Patches.htm |
| 514 | +* http://www.xiph.org/archives/vorbis/200109/0459.html |
| 515 | +* http://www.replaygain.org/ |
| 516 | +* http://www.lossless-audio.com/ |
| 517 | +* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe |
| 518 | +* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf |
| 519 | +* http://www.uni-jena.de/~pfk/mpp/sv8/ |
| 520 | +* http://jfaul.de/atl/ |
| 521 | +* http://www.uni-jena.de/~pfk/mpp/ |
| 522 | +* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html |
| 523 | +* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm |
| 524 | +* http://www.fastgraph.com/help/bmp_os2_header_format.html |
| 525 | +* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm |
| 526 | +* http://flac.sourceforge.net/format.html |
| 527 | +* http://www.research.att.com/projects/mpegaudio/mpeg2.html |
| 528 | +* http://www.audiocoding.com/wiki/index.php?page=AAC |
| 529 | +* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf |
| 530 | +* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt |
| 531 | +* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm |
| 532 | +* http://www.nullsoft.com/nsv/ |
| 533 | +* http://www.wotsit.org/download.asp?f=iso9660 |
| 534 | +* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html |
| 535 | +* http://www.cdroller.com/htm/readdata.html |
| 536 | +* http://www.speex.org/manual/node10.html |
| 537 | +* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc |
| 538 | +* http://www.faqs.org/rfcs/rfc2361.html |
| 539 | +* http://ghido.shelter.ro/ |
| 540 | +* http://www.ebu.ch/tech_t3285.pdf |
| 541 | +* http://www.sr.se/utveckling/tu/bwf |
| 542 | +* http://ftp.aessc.org/pub/aes46-2002.pdf |
| 543 | +* http://cartchunk.org:8080/ |
| 544 | +* http://www.broadcastpapers.com/radio/cartchunk01.htm |
| 545 | +* http://www.hr/josip/DSP/AudioFile2.html |
| 546 | +* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html |
| 547 | +* http://www.pure-mac.com/extkey.html |
| 548 | +* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt |
| 549 | +* http://www.headbands.com/gspot/ |
| 550 | +* http://www.openswf.org/spec/SWFfileformat.html |
| 551 | +* http://j-faul.virtualave.net/ |
| 552 | +* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html |
| 553 | +* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html |
| 554 | +* http://sswf.sourceforge.net/SWFalexref.html |
| 555 | +* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt |
| 556 | +* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm |
| 557 | +* http://developer.apple.com/quicktime/icefloe/dispatch012.html |
| 558 | +* http://www.csdn.net/Dev/Format/graphics/PCD.htm |
| 559 | +* http://tta.iszf.irk.ru/ |
| 560 | +* http://www.atsc.org/standards/a_52a.pdf |
| 561 | +* http://www.alanwood.net/unicode/ |
| 562 | +* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html |
| 563 | +* http://www.its.msstate.edu/net/real/reports/config/tags.stats |
| 564 | +* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt |
| 565 | +* http://brennan.young.net/Comp/LiveStage/things.html |
| 566 | +* http://www.multiweb.cz/twoinches/MP3inside.htm |
| 567 | +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended |
| 568 | +* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ |
| 569 | +* http://www.unicode.org/unicode/faq/utf_bom.html |
| 570 | +* http://tta.corecodec.org/?menu=format |
| 571 | +* http://www.scvi.net/nsvformat.htm |
| 572 | +* http://pda.etsi.org/pda/queryform.asp |
| 573 | +* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm |
| 574 | +* http://trac.musepack.net/trac/wiki/SV8Specification |
| 575 | +* http://wyday.com/cuesharp/specification.php |
\ No newline at end of file |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/getid3/readme.txt |
___________________________________________________________________ |
Added: svn:mime-type |
1 | 576 | + text/plain |
Index: trunk/extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php |
— | — | @@ -0,0 +1,91 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * WebM handler |
| 5 | + */ |
| 6 | +class WebMHandler extends TimedMediaHandler { |
| 7 | + const METADATA_VERSION = 1; |
| 8 | + |
| 9 | + function getMetadata( $image, $path ) { |
| 10 | + $metadata = array( 'version' => self::METADATA_VERSION ); |
| 11 | + |
| 12 | + $getID3 = new getID3(); |
| 13 | + |
| 14 | + // Don't grab stuff we don't use: |
| 15 | + $getID3->option_tag_id3v1 = false; // Read and process ID3v1 tags |
| 16 | + $getID3->option_tag_id3v2 = false; // Read and process ID3v2 tags |
| 17 | + $getID3->option_tag_lyrics3 = false; // Read and process Lyrics3 tags |
| 18 | + $getID3->option_tag_apetag = false; // Read and process APE tags |
| 19 | + $getID3->option_tags_process = false; // Copy tags to root key 'tags' and encode to $this->encoding |
| 20 | + $getID3->option_tags_html = false; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities |
| 21 | + |
| 22 | + // Analyze file and store returned data in $ThisFileInfo |
| 23 | + $id3 = $getID3->analyze( $path ); |
| 24 | + // Unset some parts of id3 that are too detailed and matroska specific: |
| 25 | + unset( $id3['matroska'] ); |
| 26 | + // remove file paths |
| 27 | + unset( $id3['filename'] ); |
| 28 | + unset( $id3['filepath'] ); |
| 29 | + unset( $id3['filenamepath']); |
| 30 | + return serialize( $id3 ); |
| 31 | + } |
| 32 | + |
| 33 | + /** |
| 34 | + * Get the "media size" |
| 35 | + * |
| 36 | + */ |
| 37 | + function getImageSize( $file, $path, $metadata = false ) { |
| 38 | + global $wgMediaVideoTypes; |
| 39 | + // Just return the size of the first video stream |
| 40 | + if ( $metadata === false ) { |
| 41 | + $metadata = $file->getMetadata(); |
| 42 | + } |
| 43 | + $metadata = $this->unpackMetadata( $metadata ); |
| 44 | + if ( isset( $metadata['error'] ) || !isset( $metadata['streams'] ) ) { |
| 45 | + return false; |
| 46 | + } |
| 47 | + foreach ( $metadata['video'] as $stream ) { |
| 48 | + return array( |
| 49 | + $stream['resolution_x'], |
| 50 | + $stream['resolution_y'] |
| 51 | + ); |
| 52 | + } |
| 53 | + return array( false, false ); |
| 54 | + } |
| 55 | + |
| 56 | + function unpackMetadata( $metadata ) { |
| 57 | + $unser = @unserialize( $metadata ); |
| 58 | + if ( isset( $unser['version'] ) && $unser['version'] == self::METADATA_VERSION ) { |
| 59 | + return $unser; |
| 60 | + } else { |
| 61 | + return false; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + function getMetadataType( $image = '' ) { |
| 66 | + return 'webm'; |
| 67 | + } |
| 68 | + |
| 69 | + function getStreamTypes( $file ) { |
| 70 | + $streamTypes = ''; |
| 71 | + $metadata = self::unpackMetadata( $file->getMetadata() ); |
| 72 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 73 | + return false; |
| 74 | + } |
| 75 | + print_r( $metadata ); |
| 76 | + die(); |
| 77 | + foreach ( $metadata['streams'] as $stream ) { |
| 78 | + $streamTypes[$stream['type']] = true; |
| 79 | + } |
| 80 | + return array_keys( $streamTypes ); |
| 81 | + } |
| 82 | + |
| 83 | + function getLength( $file ) { |
| 84 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 85 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 86 | + return 0; |
| 87 | + } else { |
| 88 | + return $metadata['playtime_seconds']; |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | +} |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/OggHandler.php |
— | — | @@ -0,0 +1,113 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * ogg handler |
| 5 | + */ |
| 6 | +class OggHandler extends TimedMediaHandler { |
| 7 | + const METADATA_VERSION = 2; |
| 8 | + |
| 9 | + function getMetadata( $image, $path ) { |
| 10 | + $metadata = array( 'version' => self::METADATA_VERSION ); |
| 11 | + |
| 12 | + if ( !class_exists( 'File_Ogg' ) ) { |
| 13 | + require( 'File/Ogg.php' ); |
| 14 | + } |
| 15 | + try { |
| 16 | + $f = new File_Ogg( $path ); |
| 17 | + $streams = array(); |
| 18 | + foreach ( $f->listStreams() as $streamType => $streamIDs ) { |
| 19 | + foreach ( $streamIDs as $streamID ) { |
| 20 | + $stream = $f->getStream( $streamID ); |
| 21 | + $streams[$streamID] = array( |
| 22 | + 'serial' => $stream->getSerial(), |
| 23 | + 'group' => $stream->getGroup(), |
| 24 | + 'type' => $stream->getType(), |
| 25 | + 'vendor' => $stream->getVendor(), |
| 26 | + 'length' => $stream->getLength(), |
| 27 | + 'size' => $stream->getSize(), |
| 28 | + 'header' => $stream->getHeader(), |
| 29 | + 'comments' => $stream->getComments() |
| 30 | + ); |
| 31 | + } |
| 32 | + } |
| 33 | + $metadata['streams'] = $streams; |
| 34 | + $metadata['length'] = $f->getLength(); |
| 35 | + // Get the offset of the file (in cases where the file is a segment copy) |
| 36 | + $metadata['offset'] = $f->getStartOffset(); |
| 37 | + } catch ( PEAR_Exception $e ) { |
| 38 | + // File not found, invalid stream, etc. |
| 39 | + $metadata['error'] = array( |
| 40 | + 'message' => $e->getMessage(), |
| 41 | + 'code' => $e->getCode() |
| 42 | + ); |
| 43 | + } |
| 44 | + return serialize( $metadata ); |
| 45 | + } |
| 46 | + /** |
| 47 | + * Get the "media size" |
| 48 | + * |
| 49 | + */ |
| 50 | + function getImageSize( $file, $path, $metadata = false ) { |
| 51 | + global $wgMediaVideoTypes; |
| 52 | + // Just return the size of the first video stream |
| 53 | + if ( $metadata === false ) { |
| 54 | + $metadata = $file->getMetadata(); |
| 55 | + } |
| 56 | + $metadata = $this->unpackMetadata( $metadata ); |
| 57 | + if ( isset( $metadata['error'] ) || !isset( $metadata['streams'] ) ) { |
| 58 | + return false; |
| 59 | + } |
| 60 | + foreach ( $metadata['streams'] as $stream ) { |
| 61 | + if ( in_array( $stream['type'], $wgMediaVideoTypes ) ) { |
| 62 | + return array( |
| 63 | + $stream['header']['PICW'], |
| 64 | + $stream['header']['PICH'] |
| 65 | + ); |
| 66 | + } |
| 67 | + } |
| 68 | + return array( false, false ); |
| 69 | + } |
| 70 | + |
| 71 | + function unpackMetadata( $metadata ) { |
| 72 | + $unser = @unserialize( $metadata ); |
| 73 | + if ( isset( $unser['version'] ) && $unser['version'] == self::METADATA_VERSION ) { |
| 74 | + return $unser; |
| 75 | + } else { |
| 76 | + return false; |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + function getMetadataType( $image = '' ) { |
| 81 | + return 'ogg'; |
| 82 | + } |
| 83 | + |
| 84 | + function getStreamTypes( $file ) { |
| 85 | + $streamTypes = ''; |
| 86 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 87 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 88 | + return false; |
| 89 | + } |
| 90 | + foreach ( $metadata['streams'] as $stream ) { |
| 91 | + $streamTypes[$stream['type']] = true; |
| 92 | + } |
| 93 | + return array_keys( $streamTypes ); |
| 94 | + } |
| 95 | + |
| 96 | + function getOffset( $file ){ |
| 97 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 98 | + if ( !$metadata || isset( $metadata['error'] ) || !isset( $metadata['offset']) ) { |
| 99 | + return 0; |
| 100 | + } else { |
| 101 | + return $metadata['offset']; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + function getLength( $file ) { |
| 106 | + $metadata = $this->unpackMetadata( $file->getMetadata() ); |
| 107 | + if ( !$metadata || isset( $metadata['error'] ) ) { |
| 108 | + return 0; |
| 109 | + } else { |
| 110 | + return $metadata['length']; |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | +} |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg.php |
— | — | @@ -0,0 +1,627 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +/** |
| 26 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 27 | + * @category File |
| 28 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 29 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 30 | + * @link http://pear.php.net/package/File_Ogg |
| 31 | + * @package File_Ogg |
| 32 | + * @version CVS: $Id: Ogg.php,v 1.14 2005/11/19 09:06:30 djg Exp $ |
| 33 | + */ |
| 34 | + |
| 35 | +/** |
| 36 | + * @access public |
| 37 | + */ |
| 38 | +define("OGG_STREAM_VORBIS", 1); |
| 39 | +/** |
| 40 | + * @access public |
| 41 | + */ |
| 42 | +define("OGG_STREAM_THEORA", 2); |
| 43 | +/** |
| 44 | + * @access public |
| 45 | + */ |
| 46 | +define("OGG_STREAM_SPEEX", 3); |
| 47 | +/** |
| 48 | + * @access public |
| 49 | + */ |
| 50 | +define("OGG_STREAM_FLAC", 4); |
| 51 | + |
| 52 | +/** |
| 53 | + * Capture pattern to determine if a file is an Ogg physical stream. |
| 54 | + * |
| 55 | + * @access private |
| 56 | + */ |
| 57 | +define("OGG_CAPTURE_PATTERN", "OggS"); |
| 58 | +/** |
| 59 | + * Maximum size of an Ogg stream page plus four. This value is specified to allow |
| 60 | + * efficient parsing of the physical stream. The extra four is a paranoid measure |
| 61 | + * to make sure a capture pattern is not split into two parts accidentally. |
| 62 | + * |
| 63 | + * @access private |
| 64 | + */ |
| 65 | +define("OGG_MAXIMUM_PAGE_SIZE", 65311); |
| 66 | +/** |
| 67 | + * Capture pattern for an Ogg Vorbis logical stream. |
| 68 | + * |
| 69 | + * @access private |
| 70 | + */ |
| 71 | +define("OGG_STREAM_CAPTURE_VORBIS", "vorbis"); |
| 72 | +/** |
| 73 | + * Capture pattern for an Ogg Speex logical stream. |
| 74 | + * @access private |
| 75 | + */ |
| 76 | +define("OGG_STREAM_CAPTURE_SPEEX", "Speex "); |
| 77 | +/** |
| 78 | + * Capture pattern for an Ogg FLAC logical stream. |
| 79 | + * |
| 80 | + * @access private |
| 81 | + */ |
| 82 | +define("OGG_STREAM_CAPTURE_FLAC", "FLAC"); |
| 83 | +/** |
| 84 | + * Capture pattern for an Ogg Theora logical stream. |
| 85 | + * |
| 86 | + * @access private |
| 87 | + */ |
| 88 | +define("OGG_STREAM_CAPTURE_THEORA", "theora"); |
| 89 | +/** |
| 90 | + * Error thrown if the file location passed is nonexistant or unreadable. |
| 91 | + * |
| 92 | + * @access private |
| 93 | + */ |
| 94 | +define("OGG_ERROR_INVALID_FILE", 1); |
| 95 | +/** |
| 96 | + * Error thrown if the user attempts to extract an unsupported logical stream. |
| 97 | + * |
| 98 | + * @access private |
| 99 | + */ |
| 100 | +define("OGG_ERROR_UNSUPPORTED", 2); |
| 101 | +/** |
| 102 | + * Error thrown if the user attempts to extract an logical stream with no |
| 103 | + * corresponding serial number. |
| 104 | + * |
| 105 | + * @access private |
| 106 | + */ |
| 107 | +define("OGG_ERROR_BAD_SERIAL", 3); |
| 108 | +/** |
| 109 | + * Error thrown if the stream appears to be corrupted. |
| 110 | + * |
| 111 | + * @access private |
| 112 | + */ |
| 113 | +define("OGG_ERROR_UNDECODABLE", 4); |
| 114 | + |
| 115 | +require_once('PEAR.php'); |
| 116 | +require_once('PEAR/Exception.php'); |
| 117 | +require_once('File/Ogg/Bitstream.php'); |
| 118 | +require_once("File/Ogg/Flac.php"); |
| 119 | +require_once("File/Ogg/Speex.php"); |
| 120 | +require_once("File/Ogg/Theora.php"); |
| 121 | +require_once("File/Ogg/Vorbis.php"); |
| 122 | + |
| 123 | + |
| 124 | +/** |
| 125 | + * Class for parsing a ogg bitstream. |
| 126 | + * |
| 127 | + * This class provides a means to access several types of logical bitstreams (e.g. Vorbis) |
| 128 | + * within a Ogg physical bitstream. |
| 129 | + * |
| 130 | + * @link http://www.xiph.org/ogg/doc/ |
| 131 | + * @package File_Ogg |
| 132 | + */ |
| 133 | +class File_Ogg |
| 134 | +{ |
| 135 | + /** |
| 136 | + * File pointer to Ogg container. |
| 137 | + * |
| 138 | + * This is the file pointer used for extracting data from the Ogg stream. It is |
| 139 | + * the result of a standard fopen call. |
| 140 | + * |
| 141 | + * @var pointer |
| 142 | + * @access private |
| 143 | + */ |
| 144 | + var $_filePointer; |
| 145 | + |
| 146 | + /** |
| 147 | + * The container for all logical streams. |
| 148 | + * |
| 149 | + * List of all of the unique streams in the Ogg physical stream. The key |
| 150 | + * used is the unique serial number assigned to the logical stream by the |
| 151 | + * encoding application. |
| 152 | + * |
| 153 | + * @var array |
| 154 | + * @access private |
| 155 | + */ |
| 156 | + var $_streamList = array(); |
| 157 | + var $_streams = array(); |
| 158 | + |
| 159 | + /** |
| 160 | + * Length in seconds of each stream group |
| 161 | + */ |
| 162 | + var $_groupLengths = array(); |
| 163 | + |
| 164 | + /** |
| 165 | + * Total length in seconds of the entire file |
| 166 | + */ |
| 167 | + var $_totalLength; |
| 168 | + var $_startOffset = false; |
| 169 | + |
| 170 | + /** |
| 171 | + * Maximum number of pages to store detailed metadata for, per stream. |
| 172 | + * We can't store every page because there could be millions, causing an OOM. |
| 173 | + * This must be big enough so that all the codecs can get the metadata they |
| 174 | + * need without re-reading the file. |
| 175 | + */ |
| 176 | + var $_maxPageCacheSize = 4; |
| 177 | + |
| 178 | + /** |
| 179 | + * Returns an interface to an Ogg physical stream. |
| 180 | + * |
| 181 | + * This method takes the path to a local file and examines it for a physical |
| 182 | + * ogg bitsream. After instantiation, the user should query the object for |
| 183 | + * the logical bitstreams held within the ogg container. |
| 184 | + * |
| 185 | + * @access public |
| 186 | + * @param string $fileLocation The path of the file to be examined. |
| 187 | + */ |
| 188 | + function __construct($fileLocation) |
| 189 | + { |
| 190 | + clearstatcache(); |
| 191 | + if (! file_exists($fileLocation)) { |
| 192 | + throw new PEAR_Exception("Couldn't Open File. Check File Path.", OGG_ERROR_INVALID_FILE); |
| 193 | + } |
| 194 | + |
| 195 | + // Open this file as a binary, and split the file into streams. |
| 196 | + $this->_filePointer = fopen($fileLocation, "rb"); |
| 197 | + if (!is_resource($this->_filePointer)) |
| 198 | + throw new PEAR_Exception("Couldn't Open File. Check File Permissions.", OGG_ERROR_INVALID_FILE); |
| 199 | + |
| 200 | + // Check for a stream at the start |
| 201 | + $magic = fread($this->_filePointer, strlen(OGG_CAPTURE_PATTERN)); |
| 202 | + if ($magic !== OGG_CAPTURE_PATTERN) { |
| 203 | + throw new PEAR_Exception("Couldn't read file: Incorrect magic number.", OGG_ERROR_UNDECODABLE); |
| 204 | + } |
| 205 | + fseek($this->_filePointer, 0, SEEK_SET); |
| 206 | + |
| 207 | + $this->_splitStreams(); |
| 208 | + fclose($this->_filePointer); |
| 209 | + } |
| 210 | + |
| 211 | + /** |
| 212 | + * Little-endian equivalent for bin2hex |
| 213 | + * @static |
| 214 | + */ |
| 215 | + static function _littleEndianBin2Hex( $bin ) { |
| 216 | + $bigEndian = bin2hex( $bin ); |
| 217 | + // Reverse entire string |
| 218 | + $reversed = strrev( $bigEndian ); |
| 219 | + // Swap nibbles back |
| 220 | + for ( $i = 0; $i < strlen( $bigEndian ); $i += 2 ) { |
| 221 | + $temp = $reversed[$i]; |
| 222 | + $reversed[$i] = $reversed[$i+1]; |
| 223 | + $reversed[$i+1] = $temp; |
| 224 | + } |
| 225 | + return $reversed; |
| 226 | + } |
| 227 | + |
| 228 | + |
| 229 | + /** |
| 230 | + * Read a binary structure from a file. An array of unsigned integers are read. |
| 231 | + * Large integers are upgraded to floating point on overflow. |
| 232 | + * |
| 233 | + * Format is big-endian as per Theora bit packing convention, this function |
| 234 | + * won't work for Vorbis. |
| 235 | + * |
| 236 | + * @param resource $file |
| 237 | + * @param array $fields Associative array mapping name to length in bits |
| 238 | + */ |
| 239 | + static function _readBigEndian($file, $fields) |
| 240 | + { |
| 241 | + $bufferLength = ceil(array_sum($fields) / 8); |
| 242 | + $buffer = fread($file, $bufferLength); |
| 243 | + if (strlen($buffer) != $bufferLength) { |
| 244 | + throw new PEAR_Exception('Unexpected end of file', OGG_ERROR_UNDECODABLE); |
| 245 | + } |
| 246 | + $bytePos = 0; |
| 247 | + $bitPos = 0; |
| 248 | + $byteValue = ord($buffer[0]); |
| 249 | + $output = array(); |
| 250 | + foreach ($fields as $name => $width) { |
| 251 | + if ($width % 8 == 0 && $bitPos == 0) { |
| 252 | + // Byte aligned case |
| 253 | + $bytes = $width / 8; |
| 254 | + $endBytePos = $bytePos + $bytes; |
| 255 | + $value = 0; |
| 256 | + while ($bytePos < $endBytePos) { |
| 257 | + $value = ($value * 256) + ord($buffer[$bytePos]); |
| 258 | + $bytePos++; |
| 259 | + } |
| 260 | + if ($bytePos < strlen($buffer)) { |
| 261 | + $byteValue = ord($buffer[$bytePos]); |
| 262 | + } |
| 263 | + } else { |
| 264 | + // General case |
| 265 | + $bitsRemaining = $width; |
| 266 | + $value = 0; |
| 267 | + while ($bitsRemaining > 0) { |
| 268 | + $bitsToRead = min($bitsRemaining, 8 - $bitPos); |
| 269 | + $byteValue <<= $bitsToRead; |
| 270 | + $overflow = ($byteValue & 0xff00) >> 8; |
| 271 | + $byteValue &= $byteValue & 0xff; |
| 272 | + |
| 273 | + $bitPos += $bitsToRead; |
| 274 | + $bitsRemaining -= $bitsToRead; |
| 275 | + $value += $overflow * pow(2, $bitsRemaining); |
| 276 | + |
| 277 | + if ($bitPos >= 8) { |
| 278 | + $bitPos = 0; |
| 279 | + $bytePos++; |
| 280 | + if ($bitsRemaining <= 0) { |
| 281 | + break; |
| 282 | + } |
| 283 | + $byteValue = ord($buffer[$bytePos]); |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + $output[$name] = $value; |
| 288 | + assert($bytePos <= $bufferLength); |
| 289 | + } |
| 290 | + return $output; |
| 291 | + } |
| 292 | + |
| 293 | + /** |
| 294 | + * Read a binary structure from a file. An array of unsigned integers are read. |
| 295 | + * Large integers are upgraded to floating point on overflow. |
| 296 | + * |
| 297 | + * Format is little-endian as per Vorbis bit packing convention. |
| 298 | + * |
| 299 | + * @param resource $file |
| 300 | + * @param array $fields Associative array mapping name to length in bits |
| 301 | + */ |
| 302 | + static function _readLittleEndian( $file, $fields ) { |
| 303 | + $bufferLength = ceil(array_sum($fields) / 8); |
| 304 | + $buffer = fread($file, $bufferLength); |
| 305 | + if (strlen($buffer) != $bufferLength) { |
| 306 | + throw new PEAR_Exception('Unexpected end of file', OGG_ERROR_UNDECODABLE); |
| 307 | + } |
| 308 | + |
| 309 | + $bytePos = 0; |
| 310 | + $bitPos = 0; |
| 311 | + $byteValue = ord($buffer[0]) << 8; |
| 312 | + $output = array(); |
| 313 | + foreach ($fields as $name => $width) { |
| 314 | + if ($width % 8 == 0 && $bitPos == 0) { |
| 315 | + // Byte aligned case |
| 316 | + $bytes = $width / 8; |
| 317 | + $value = 0; |
| 318 | + for ($i = 0; $i < $bytes; $i++, $bytePos++) { |
| 319 | + $value += pow(256, $i) * ord($buffer[$bytePos]); |
| 320 | + } |
| 321 | + if ($bytePos < strlen($buffer)) { |
| 322 | + $byteValue = ord($buffer[$bytePos]) << 8; |
| 323 | + } |
| 324 | + } else { |
| 325 | + // General case |
| 326 | + $bitsRemaining = $width; |
| 327 | + $value = 0; |
| 328 | + while ($bitsRemaining > 0) { |
| 329 | + $bitsToRead = min($bitsRemaining, 8 - $bitPos); |
| 330 | + $byteValue >>= $bitsToRead; |
| 331 | + $overflow = ($byteValue & 0xff) >> (8 - $bitsToRead); |
| 332 | + $byteValue &= 0xff00; |
| 333 | + |
| 334 | + $value += $overflow * pow(2, $width - $bitsRemaining); |
| 335 | + $bitPos += $bitsToRead; |
| 336 | + $bitsRemaining -= $bitsToRead; |
| 337 | + |
| 338 | + if ($bitPos >= 8) { |
| 339 | + $bitPos = 0; |
| 340 | + $bytePos++; |
| 341 | + if ($bitsRemaining <= 0) { |
| 342 | + break; |
| 343 | + } |
| 344 | + $byteValue = ord($buffer[$bytePos]) << 8; |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + $output[$name] = $value; |
| 349 | + assert($bytePos <= $bufferLength); |
| 350 | + } |
| 351 | + return $output; |
| 352 | + } |
| 353 | + |
| 354 | + |
| 355 | + /** |
| 356 | + * @access private |
| 357 | + */ |
| 358 | + function _decodePageHeader($pageData, $pageOffset, $groupId) |
| 359 | + { |
| 360 | + // Extract the various bits and pieces found in each packet header. |
| 361 | + if (substr($pageData, 0, 4) != OGG_CAPTURE_PATTERN) |
| 362 | + return (false); |
| 363 | + |
| 364 | + $stream_version = unpack("C1data", substr($pageData, 4, 1)); |
| 365 | + if ($stream_version['data'] != 0x00) |
| 366 | + return (false); |
| 367 | + |
| 368 | + $header_flag = unpack("Cdata", substr($pageData, 5, 1)); |
| 369 | + |
| 370 | + // Exact granule position |
| 371 | + $abs_granule_pos = self::_littleEndianBin2Hex( substr($pageData, 6, 8)); |
| 372 | + |
| 373 | + // Approximate (floating point) granule position |
| 374 | + $pos = unpack("Va/Vb", substr($pageData, 6, 8)); |
| 375 | + $approx_granule_pos = $pos['a'] + $pos['b'] * pow(2, 32); |
| 376 | + |
| 377 | + // Serial number for the current datastream. |
| 378 | + $stream_serial = unpack("Vdata", substr($pageData, 14, 4)); |
| 379 | + $page_sequence = unpack("Vdata", substr($pageData, 18, 4)); |
| 380 | + $checksum = unpack("Vdata", substr($pageData, 22, 4)); |
| 381 | + $page_segments = unpack("Cdata", substr($pageData, 26, 1)); |
| 382 | + $segments_total = 0; |
| 383 | + for ($i = 0; $i < $page_segments['data']; ++$i) { |
| 384 | + $segment_length = unpack("Cdata", substr($pageData, 26 + ($i + 1), 1)); |
| 385 | + $segments_total += $segment_length['data']; |
| 386 | + } |
| 387 | + $pageFinish = $pageOffset + 27 + $page_segments['data'] + $segments_total; |
| 388 | + $page = array( |
| 389 | + 'stream_version' => $stream_version['data'], |
| 390 | + 'header_flag' => $header_flag['data'], |
| 391 | + 'abs_granule_pos' => $abs_granule_pos, |
| 392 | + 'approx_granule_pos' => $approx_granule_pos, |
| 393 | + 'checksum' => sprintf("%u", $checksum['data']), |
| 394 | + 'segments' => $page_segments['data'], |
| 395 | + 'head_offset' => $pageOffset, |
| 396 | + 'body_offset' => $pageOffset + 27 + $page_segments['data'], |
| 397 | + 'body_finish' => $pageFinish, |
| 398 | + 'data_length' => $pageFinish - $pageOffset, |
| 399 | + 'group' => $groupId, |
| 400 | + ); |
| 401 | + if ( !isset( $this->_streamList[$stream_serial['data']] ) ) { |
| 402 | + $this->_streamList[$stream_serial['data']] = array( |
| 403 | + 'pages' => array(), |
| 404 | + 'data_length' => 0, |
| 405 | + 'first_granule_pos' => null, |
| 406 | + 'last_granule_pos' => null, |
| 407 | + ); |
| 408 | + } |
| 409 | + $stream =& $this->_streamList[$stream_serial['data']]; |
| 410 | + if ( count( $stream['pages'] ) < $this->_maxPageCacheSize ) { |
| 411 | + $stream['pages'][$page_sequence['data']] = $page; |
| 412 | + } |
| 413 | + $stream['last_page'] = $page; |
| 414 | + $stream['data_length'] += $page['data_length']; |
| 415 | + |
| 416 | + # Reject -1 as a granule pos, that means no segment finished in the packet |
| 417 | + if ( $abs_granule_pos !== 'ffffffffffffffff' ) { |
| 418 | + if ( $stream['first_granule_pos'] === null ) { |
| 419 | + $stream['first_granule_pos'] = $abs_granule_pos; |
| 420 | + } |
| 421 | + $stream['last_granule_pos'] = $abs_granule_pos; |
| 422 | + } |
| 423 | + |
| 424 | + $pageData = null; |
| 425 | + return $page; |
| 426 | + } |
| 427 | + |
| 428 | + /** |
| 429 | + * @access private |
| 430 | + */ |
| 431 | + function _splitStreams() |
| 432 | + { |
| 433 | + // Loop through the physical stream until there are no more pages to read. |
| 434 | + $groupId = 0; |
| 435 | + $openStreams = 0; |
| 436 | + $this_page_offset = 0; |
| 437 | + while (!feof($this->_filePointer)) { |
| 438 | + $pageData = fread($this->_filePointer, 282); |
| 439 | + if (strval($pageData) === '') { |
| 440 | + break; |
| 441 | + } |
| 442 | + $page = $this->_decodePageHeader($pageData, $this_page_offset, $groupId); |
| 443 | + if ($page === false) { |
| 444 | + throw new PEAR_Exception("Cannot decode Ogg file: Invalid page at offset $this_page_offset", OGG_ERROR_UNDECODABLE); |
| 445 | + } |
| 446 | + |
| 447 | + // Keep track of multiplexed groups |
| 448 | + if ($page['header_flag'] & 2/*bos*/) { |
| 449 | + $openStreams++; |
| 450 | + } elseif ($page['header_flag'] & 4/*eos*/) { |
| 451 | + $openStreams--; |
| 452 | + if (!$openStreams) { |
| 453 | + // End of group |
| 454 | + $groupId++; |
| 455 | + } |
| 456 | + } |
| 457 | + if ($openStreams < 0) { |
| 458 | + throw new PEAR_Exception("Unexpected end of stream", OGG_ERROR_UNDECODABLE); |
| 459 | + } |
| 460 | + |
| 461 | + $this_page_offset = $page['body_finish']; |
| 462 | + fseek( $this->_filePointer, $this_page_offset, SEEK_SET ); |
| 463 | + } |
| 464 | + // Loop through the streams, and find out what type of stream is available. |
| 465 | + $groupLengths = array(); |
| 466 | + foreach ($this->_streamList as $stream_serial => $streamData) { |
| 467 | + fseek($this->_filePointer, $streamData['pages'][0]['body_offset'], SEEK_SET); |
| 468 | + $pattern = fread($this->_filePointer, 8); |
| 469 | + if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) { |
| 470 | + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS; |
| 471 | + $stream = new File_Ogg_Vorbis($stream_serial, $streamData, $this->_filePointer); |
| 472 | + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) { |
| 473 | + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX; |
| 474 | + $stream = new File_Ogg_Speex($stream_serial, $streamData, $this->_filePointer); |
| 475 | + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) { |
| 476 | + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC; |
| 477 | + $stream = new File_Ogg_Flac($stream_serial, $streamData, $this->_filePointer); |
| 478 | + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) { |
| 479 | + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA; |
| 480 | + $stream = new File_Ogg_Theora($stream_serial, $streamData, $this->_filePointer); |
| 481 | + } else { |
| 482 | + $streamData['stream_type'] = "unknown"; |
| 483 | + $stream = false; |
| 484 | + } |
| 485 | + |
| 486 | + if ($stream) { |
| 487 | + $this->_streams[$stream_serial] = $stream; |
| 488 | + $group = $streamData['pages'][0]['group']; |
| 489 | + if (isset($groupLengths[$group])) { |
| 490 | + $groupLengths[$group] = max($groupLengths[$group], $stream->getLength()); |
| 491 | + } else { |
| 492 | + $groupLengths[$group] = $stream->getLength(); |
| 493 | + } |
| 494 | + //just store the startOffset for the first stream: |
| 495 | + if( $this->_startOffset === false ){ |
| 496 | + $this->_startOffset = $stream->getStartOffset(); |
| 497 | + } |
| 498 | + |
| 499 | + } |
| 500 | + } |
| 501 | + $this->_groupLengths = $groupLengths; |
| 502 | + $this->_totalLength = array_sum( $groupLengths ); |
| 503 | + unset($this->_streamList); |
| 504 | + } |
| 505 | + |
| 506 | + /** |
| 507 | + * Returns the overead percentage used by the Ogg headers. |
| 508 | + * |
| 509 | + * This function returns the percentage of the total stream size |
| 510 | + * used for Ogg headers. |
| 511 | + * |
| 512 | + * @return float |
| 513 | + */ |
| 514 | + function getOverhead() { |
| 515 | + $header_size = 0; |
| 516 | + $stream_size = 0; |
| 517 | + foreach ($this->_streams as $serial => $stream) { |
| 518 | + foreach ($stream->_streamList as $offset => $stream_data) { |
| 519 | + $header_size += $stream_data['body_offset'] - $stream_data['head_offset']; |
| 520 | + $stream_size = $stream_data['body_finish']; |
| 521 | + } |
| 522 | + } |
| 523 | + return sprintf("%0.2f", ($header_size / $stream_size) * 100); |
| 524 | + } |
| 525 | + |
| 526 | + /** |
| 527 | + * Returns the appropriate logical bitstream that corresponds to the provided serial. |
| 528 | + * |
| 529 | + * This function returns a logical bitstream contained within the Ogg physical |
| 530 | + * stream, corresponding to the serial used as the offset for that bitstream. |
| 531 | + * The returned stream may be Vorbis, Speex, FLAC or Theora, although the only |
| 532 | + * usable bitstream is Vorbis. |
| 533 | + * |
| 534 | + * @return File_Ogg_Bitstream |
| 535 | + */ |
| 536 | + function &getStream($streamSerial) |
| 537 | + { |
| 538 | + if (! array_key_exists($streamSerial, $this->_streams)) |
| 539 | + throw new PEAR_Exception("The stream number is invalid.", OGG_ERROR_BAD_SERIAL); |
| 540 | + |
| 541 | + return $this->_streams[$streamSerial]; |
| 542 | + } |
| 543 | + |
| 544 | + /** |
| 545 | + * This function returns true if a logical bitstream of the requested type can be found. |
| 546 | + * |
| 547 | + * This function checks the contents of this ogg physical bitstream for of logical |
| 548 | + * bitstream corresponding to the supplied type. If one is found, the function returns |
| 549 | + * true, otherwise it return false. |
| 550 | + * |
| 551 | + * @param int $streamType |
| 552 | + * @return boolean |
| 553 | + */ |
| 554 | + function hasStream($streamType) |
| 555 | + { |
| 556 | + foreach ($this->_stream as $stream) { |
| 557 | + if ($stream['stream_type'] == $streamType) |
| 558 | + return (true); |
| 559 | + } |
| 560 | + return (false); |
| 561 | + } |
| 562 | + |
| 563 | + /** |
| 564 | + * Returns an array of logical streams inside this physical bitstream. |
| 565 | + * |
| 566 | + * This function returns an array of logical streams found within this physical |
| 567 | + * bitstream. If a filter is provided, only logical streams of the requested type |
| 568 | + * are returned, as an array of serial numbers. If no filter is provided, this |
| 569 | + * function returns a two-dimensional array, with the stream type as the primary key, |
| 570 | + * and a value consisting of an array of stream serial numbers. |
| 571 | + * |
| 572 | + * @param int $filter |
| 573 | + * @return array |
| 574 | + */ |
| 575 | + function listStreams($filter = null) |
| 576 | + { |
| 577 | + $streams = array(); |
| 578 | + // Loops through the streams and assign them to an appropriate index, |
| 579 | + // ready for filtering the second part of this function. |
| 580 | + foreach ($this->_streams as $serial => $stream) { |
| 581 | + $stream_type = 0; |
| 582 | + switch (get_class($stream)) { |
| 583 | + case "file_ogg_flac": |
| 584 | + $stream_type = OGG_STREAM_FLAC; |
| 585 | + break; |
| 586 | + case "file_ogg_speex": |
| 587 | + $stream_type = OGG_STREAM_SPEEX; |
| 588 | + break; |
| 589 | + case "file_ogg_theora": |
| 590 | + $stream_type = OGG_STREAM_THEORA; |
| 591 | + break; |
| 592 | + case "file_ogg_vorbis": |
| 593 | + $stream_type = OGG_STREAM_VORBIS; |
| 594 | + break; |
| 595 | + } |
| 596 | + if (! isset($streams[$stream_type])) |
| 597 | + // Initialise the result list for this stream type. |
| 598 | + $streams[$stream_type] = array(); |
| 599 | + |
| 600 | + $streams[$stream_type][] = $serial; |
| 601 | + } |
| 602 | + |
| 603 | + // Perform filtering. |
| 604 | + if (is_null($filter)) |
| 605 | + return ($streams); |
| 606 | + elseif (isset($streams[$filter])) |
| 607 | + return ($streams[$filter]); |
| 608 | + else |
| 609 | + return array(); |
| 610 | + } |
| 611 | + /** |
| 612 | + * getStartOffset |
| 613 | + * |
| 614 | + * @return unknown |
| 615 | + */ |
| 616 | + function getStartOffset(){ |
| 617 | + if( $this->_startOffset === false) |
| 618 | + return 0; |
| 619 | + return $this->_startOffset; |
| 620 | + } |
| 621 | + /** |
| 622 | + * Get the total length of the group of streams |
| 623 | + */ |
| 624 | + function getLength() { |
| 625 | + return $this->_totalLength; |
| 626 | + } |
| 627 | +} |
| 628 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 629 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Bitstream.php |
— | — | @@ -0,0 +1,125 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | + |
| 26 | +/** |
| 27 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 28 | + * @category File |
| 29 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 30 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 31 | + * @link http://pear.php.net/package/File_Ogg |
| 32 | + * @package File_Ogg |
| 33 | + * @version CVS: $Id: Bitstream.php,v 1.3 2005/11/08 19:36:18 djg Exp $ |
| 34 | + */ |
| 35 | +class File_Ogg_Bitstream |
| 36 | +{ |
| 37 | + /** |
| 38 | + * The serial number of this logical stream. |
| 39 | + * |
| 40 | + * @var int |
| 41 | + * @access private |
| 42 | + */ |
| 43 | + var $_streamSerial; |
| 44 | + /** |
| 45 | + * @access private |
| 46 | + */ |
| 47 | + var $_streamData; |
| 48 | + /** |
| 49 | + * @access private |
| 50 | + */ |
| 51 | + var $_filePointer; |
| 52 | + /** |
| 53 | + * The number of bits used in this stream. |
| 54 | + * |
| 55 | + * @var int |
| 56 | + * @access private |
| 57 | + */ |
| 58 | + var $_streamSize; |
| 59 | + |
| 60 | + /** |
| 61 | + * The last granule position in the stream |
| 62 | + * @var int |
| 63 | + * @access private |
| 64 | + */ |
| 65 | + var $_lastGranulePos; |
| 66 | + |
| 67 | + /** |
| 68 | + * Constructor for a generic logical stream. |
| 69 | + * |
| 70 | + * @param int $streamSerial Serial number of the logical stream. |
| 71 | + * @param array $streamData Data for the requested logical stream. |
| 72 | + * @param string $filePath Location of a file on the filesystem. |
| 73 | + * @param pointer $filePointer File pointer for the current physical stream. |
| 74 | + * @access private |
| 75 | + */ |
| 76 | + function __construct($streamSerial, $streamData, $filePointer) |
| 77 | + { |
| 78 | + $this->_streamSerial = $streamSerial; |
| 79 | + $this->_streamData = $streamData; |
| 80 | + $this->_filePointer = $filePointer; |
| 81 | + $this->_firstGranulePos = $streamData['first_granule_pos']; |
| 82 | + $this->_lastGranulePos = $streamData['last_granule_pos']; |
| 83 | + $this->_streamSize = $streamData['data_length']; |
| 84 | + $this->_group = $streamData['pages'][0]['group']; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Gives the serial number of this stream. |
| 89 | + * |
| 90 | + * The stream serial number is of fairly academic importance, as it makes little |
| 91 | + * difference to the end user. The serial number is used by the Ogg physical |
| 92 | + * stream to distinguish between concurrent logical streams. |
| 93 | + * |
| 94 | + * @return int |
| 95 | + * @access public |
| 96 | + */ |
| 97 | + function getSerial() |
| 98 | + { |
| 99 | + return ($this->_streamSerial); |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * Gives the size (in bits) of this stream. |
| 104 | + * |
| 105 | + * This function returns the size of the Vorbis stream within the Ogg |
| 106 | + * physical stream. |
| 107 | + * |
| 108 | + * @return int |
| 109 | + * @access public |
| 110 | + */ |
| 111 | + function getSize() |
| 112 | + { |
| 113 | + return ($this->_streamSize); |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * Get the multiplexed group ID |
| 118 | + */ |
| 119 | + function getGroup() |
| 120 | + { |
| 121 | + return $this->_group; |
| 122 | + } |
| 123 | + |
| 124 | +} |
| 125 | + |
| 126 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Bitstream.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 127 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Media.php |
— | — | @@ -0,0 +1,233 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +require_once('File/Ogg/Bitstream.php'); |
| 26 | + |
| 27 | +/** |
| 28 | + * Parent class for media bitstreams |
| 29 | + * Contains some functions common to various media formats |
| 30 | + */ |
| 31 | +abstract class File_Ogg_Media extends File_Ogg_Bitstream |
| 32 | +{ |
| 33 | + /** |
| 34 | + * Array to hold each of the comments. |
| 35 | + * |
| 36 | + * @access private |
| 37 | + * @var array |
| 38 | + */ |
| 39 | + var $_comments = array(); |
| 40 | + /** |
| 41 | + * Vendor string for the stream. |
| 42 | + * |
| 43 | + * @access private |
| 44 | + * @var string |
| 45 | + */ |
| 46 | + var $_vendor; |
| 47 | + |
| 48 | + /** |
| 49 | + * Length of the stream in seconds |
| 50 | + */ |
| 51 | + var $_streamLength; |
| 52 | + |
| 53 | + /* Start offset of the stream in seconds */ |
| 54 | + var $_startOffset = 0; |
| 55 | + |
| 56 | + /** |
| 57 | + * Get a short string describing the type of the stream |
| 58 | + * @return string |
| 59 | + */ |
| 60 | + abstract function getType(); |
| 61 | + |
| 62 | + /** |
| 63 | + * Get the 6-byte identification string expected in the common header |
| 64 | + * @return string |
| 65 | + */ |
| 66 | + function getIdentificationString() |
| 67 | + { |
| 68 | + return ''; |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * @access private |
| 73 | + * @param int $packetType |
| 74 | + * @param int $pageOffset |
| 75 | + */ |
| 76 | + function _decodeCommonHeader($packetType, $pageOffset) |
| 77 | + { |
| 78 | + fseek($this->_filePointer, $this->_streamData['pages'][$pageOffset]['body_offset'], SEEK_SET); |
| 79 | + if ($packetType !== false) { |
| 80 | + // Check if this is the correct header. |
| 81 | + $packet = unpack("Cdata", fread($this->_filePointer, 1)); |
| 82 | + if ($packet['data'] != $packetType) |
| 83 | + throw new PEAR_Exception("Stream Undecodable", OGG_ERROR_UNDECODABLE); |
| 84 | + |
| 85 | + // The following six characters should be equal to getIdentificationString() |
| 86 | + $id = $this->getIdentificationString(); |
| 87 | + if ($id !== '' && fread($this->_filePointer, strlen($id)) !== $id) |
| 88 | + throw new PEAR_Exception("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); |
| 89 | + } // else seek only, no common header |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Parse a Vorbis-style comments header. |
| 94 | + * |
| 95 | + * This function parses the comments header. The comments header contains a series of |
| 96 | + * UTF-8 comments related to the audio encoded in the stream. This header also contains |
| 97 | + * a string to identify the encoding software. More details on the comments header can |
| 98 | + * be found at the following location: http://xiph.org/vorbis/doc/v-comment.html |
| 99 | + * |
| 100 | + * @access private |
| 101 | + */ |
| 102 | + function _decodeBareCommentsHeader() |
| 103 | + { |
| 104 | + // Decode the vendor string length as a 32-bit unsigned integer. |
| 105 | + $vendor_len = unpack("Vdata", fread($this->_filePointer, 4)); |
| 106 | + if ( $vendor_len['data'] > 0 ) { |
| 107 | + // Retrieve the vendor string from the stream. |
| 108 | + $this->_vendor = fread($this->_filePointer, $vendor_len['data']); |
| 109 | + } else { |
| 110 | + $this->_vendor = ''; |
| 111 | + } |
| 112 | + // Decode the size of the comments list as a 32-bit unsigned integer. |
| 113 | + $comment_list_length = unpack("Vdata", fread($this->_filePointer, 4)); |
| 114 | + // Iterate through the comments list. |
| 115 | + for ($i = 0; $i < $comment_list_length['data']; ++$i) { |
| 116 | + // Unpack the length of this comment. |
| 117 | + $comment_length = unpack("Vdata", fread($this->_filePointer, 4)); |
| 118 | + // Comments are in the format 'ARTIST=Super Furry Animals', so split it on the equals character. |
| 119 | + // NOTE: Equals characters are strictly prohibited in either the COMMENT or DATA parts. |
| 120 | + $comment = explode("=", fread($this->_filePointer, $comment_length['data'])); |
| 121 | + $comment_title = (string) $comment[0]; |
| 122 | + $comment_value = (string) utf8_decode($comment[1]); |
| 123 | + |
| 124 | + // Check if the comment type (e.g. ARTIST) already exists. If it does, |
| 125 | + // take the new value, and the existing value (or array) and insert it |
| 126 | + // into a new array. This is important, since each comment type may have |
| 127 | + // multiple instances (e.g. ARTIST for a collaboration) and we should not |
| 128 | + // overwrite the previous value. |
| 129 | + if (isset($this->_comments[$comment_title])) { |
| 130 | + if (is_array($this->_comments[$comment_title])) |
| 131 | + $this->_comments[$comment_title][] = $comment_value; |
| 132 | + else |
| 133 | + $this->_comments[$comment_title] = array($this->_comments[$comment_title], $comment_value); |
| 134 | + } else |
| 135 | + $this->_comments[$comment_title] = $comment_value; |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * Provides a list of the comments extracted from the Vorbis stream. |
| 141 | + * |
| 142 | + * It is recommended that the user fully inspect the array returned by this function |
| 143 | + * rather than blindly requesting a comment in false belief that it will always |
| 144 | + * be present. Whilst the Vorbis specification dictates a number of popular |
| 145 | + * comments (e.g. TITLE, ARTIST, etc.) for use in Vorbis streams, they are not |
| 146 | + * guaranteed to appear. |
| 147 | + * |
| 148 | + * @access public |
| 149 | + * @return array |
| 150 | + */ |
| 151 | + function getCommentList() |
| 152 | + { |
| 153 | + return (array_keys($this->_comments)); |
| 154 | + } |
| 155 | + |
| 156 | + /** |
| 157 | + * Provides an interface to the numerous comments located with a Vorbis stream. |
| 158 | + * |
| 159 | + * A Vorbis stream may contain one or more instances of each comment, so the user |
| 160 | + * should check the variable type before printing out the result of this method. |
| 161 | + * The situation in which multiple instances of a comment occurring are not as |
| 162 | + * rare as one might think, since they are conceivable at least for ARTIST comments |
| 163 | + * in the situation where a track is a duet. |
| 164 | + * |
| 165 | + * @access public |
| 166 | + * @param string $commentTitle Comment title to search for, e.g. TITLE. |
| 167 | + * @param string $separator String to separate multiple values. |
| 168 | + * @return string |
| 169 | + */ |
| 170 | + function getField($commentTitle, $separator = ", ") |
| 171 | + { |
| 172 | + if (isset($this->_comments[$commentTitle])) { |
| 173 | + if (is_array($this->_comments[$commentTitle])) |
| 174 | + return (implode($separator, $this->_comments[$commentTitle])); |
| 175 | + else |
| 176 | + return ($this->_comments[$commentTitle]); |
| 177 | + } else |
| 178 | + // The comment doesn't exist in this file. The user should've called getCommentList first. |
| 179 | + return (""); |
| 180 | + } |
| 181 | + |
| 182 | + /** |
| 183 | + * Get the entire comments array. |
| 184 | + * May return an empty array if the bitstream does not support comments. |
| 185 | + * |
| 186 | + * @access public |
| 187 | + * @return array |
| 188 | + */ |
| 189 | + function getComments() { |
| 190 | + return $this->_comments; |
| 191 | + } |
| 192 | + |
| 193 | + /** |
| 194 | + * Vendor of software used to encode this stream. |
| 195 | + * |
| 196 | + * Gives the vendor string for the software used to encode this stream. |
| 197 | + * It is common to find libVorbis here. The majority of encoders appear |
| 198 | + * to use libvorbis from Xiph.org. |
| 199 | + * |
| 200 | + * @access public |
| 201 | + * @return string |
| 202 | + */ |
| 203 | + function getVendor() |
| 204 | + { |
| 205 | + return ($this->_vendor); |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Get an associative array containing header information about the stream |
| 210 | + * @access public |
| 211 | + * @return array |
| 212 | + */ |
| 213 | + function getHeader() |
| 214 | + { |
| 215 | + return array(); |
| 216 | + } |
| 217 | + |
| 218 | + /** |
| 219 | + * Get the length of the stream in seconds |
| 220 | + * @return float |
| 221 | + */ |
| 222 | + function getLength() |
| 223 | + { |
| 224 | + return $this->_streamLength; |
| 225 | + } |
| 226 | + /** |
| 227 | + * Get the start offset of the stream in seconds |
| 228 | + * |
| 229 | + * @return float |
| 230 | + */ |
| 231 | + function getStartOffset(){ |
| 232 | + return $this->_startOffset; |
| 233 | + } |
| 234 | +} |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Media.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 235 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Theora.php |
— | — | @@ -0,0 +1,241 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +require_once('File/Ogg/Bitstream.php'); |
| 26 | + |
| 27 | +define( 'OGG_THEORA_IDENTIFICATION_HEADER', 0x80 ); |
| 28 | +define( 'OGG_THEORA_COMMENTS_HEADER', 0x81 ); |
| 29 | +define( 'OGG_THEORA_IDENTIFICATION_PAGE_OFFSET', 0 ); |
| 30 | +define( 'OGG_THEORA_COMMENTS_PAGE_OFFSET', 1 ); |
| 31 | + |
| 32 | + |
| 33 | +/** |
| 34 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 35 | + * @category File |
| 36 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 37 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 38 | + * @link http://pear.php.net/package/File_Ogg |
| 39 | + * @link http://www.xiph.org/theora/ |
| 40 | + * @package File_Ogg |
| 41 | + * @version CVS: $Id: Theora.php,v 1.9 2005/11/16 20:43:27 djg Exp $ |
| 42 | + */ |
| 43 | +class File_Ogg_Theora extends File_Ogg_Media |
| 44 | +{ |
| 45 | + /** |
| 46 | + * @access private |
| 47 | + */ |
| 48 | + function __construct($streamSerial, $streamData, $filePointer) |
| 49 | + { |
| 50 | + parent::__construct($streamSerial, $streamData, $filePointer); |
| 51 | + $this->_decodeIdentificationHeader(); |
| 52 | + $this->_decodeCommentsHeader(); |
| 53 | + $endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos ); |
| 54 | + |
| 55 | + $startSec = $this->getSecondsFromGranulePos( $this->_firstGranulePos ); |
| 56 | + |
| 57 | + //make sure the offset is worth taking into account oggz_chop related hack |
| 58 | + if( $startSec > 1){ |
| 59 | + $this->_streamLength = $endSec - $startSec; |
| 60 | + $this->_startOffset = $startSec; |
| 61 | + }else{ |
| 62 | + $this->_streamLength = $endSec; |
| 63 | + } |
| 64 | + |
| 65 | + $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0; |
| 66 | + } |
| 67 | + function getSecondsFromGranulePos($granulePos){ |
| 68 | + // Calculate GranulePos seconds |
| 69 | + // First make some "numeric strings" |
| 70 | + // These might not fit into PHP's integer type, but they will fit into |
| 71 | + // the 53-bit mantissa of a double-precision number |
| 72 | + $topWord = floatval( base_convert( substr( $granulePos, 0, 8 ), 16, 10 ) ); |
| 73 | + $bottomWord = floatval( base_convert( substr( $granulePos, 8, 8 ), 16, 10 ) ); |
| 74 | + // Calculate the keyframe position by shifting right by KFGSHIFT |
| 75 | + // We don't use PHP's shift operators because they're terribly broken |
| 76 | + // This is made slightly simpler by the fact that KFGSHIFT < 32 |
| 77 | + $keyFramePos = $topWord / pow(2, $this->_kfgShift - 32) + |
| 78 | + floor( $bottomWord / pow(2, $this->_kfgShift) ); |
| 79 | + // Calculate the frame offset by masking off the top 64-KFGSHIFT bits |
| 80 | + // This requires a bit of floating point trickery |
| 81 | + $offset = fmod( $bottomWord, pow(2, $this->_kfgShift) ); |
| 82 | + // They didn't teach you that one at school did they? |
| 83 | + // Now put it together with the frame rate to calculate time in seconds |
| 84 | + return ( $keyFramePos + $offset ) / $this->_frameRate; |
| 85 | + } |
| 86 | + /** |
| 87 | + * Get the 6-byte identification string expected in the common header |
| 88 | + */ |
| 89 | + function getIdentificationString() |
| 90 | + { |
| 91 | + return OGG_STREAM_CAPTURE_THEORA; |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * Parse the identification header in a Theora stream. |
| 96 | + * @access private |
| 97 | + */ |
| 98 | + function _decodeIdentificationHeader() |
| 99 | + { |
| 100 | + $this->_decodeCommonHeader(OGG_THEORA_IDENTIFICATION_HEADER, OGG_THEORA_IDENTIFICATION_PAGE_OFFSET); |
| 101 | + $h = File_Ogg::_readBigEndian( $this->_filePointer, array( |
| 102 | + 'VMAJ' => 8, |
| 103 | + 'VMIN' => 8, |
| 104 | + 'VREV' => 8, |
| 105 | + 'FMBW' => 16, |
| 106 | + 'FMBH' => 16, |
| 107 | + 'PICW' => 24, |
| 108 | + 'PICH' => 24, |
| 109 | + 'PICX' => 8, |
| 110 | + 'PICY' => 8, |
| 111 | + 'FRN' => 32, |
| 112 | + 'FRD' => 32, |
| 113 | + 'PARN' => 24, |
| 114 | + 'PARD' => 24, |
| 115 | + 'CS' => 8, |
| 116 | + 'NOMBR' => 24, |
| 117 | + 'QUAL' => 6, |
| 118 | + 'KFGSHIFT' => 5, |
| 119 | + 'PF' => 2)); |
| 120 | + if ( !$h ) { |
| 121 | + throw new PEAR_Exception("Stream is undecodable due to a truncated header.", OGG_ERROR_UNDECODABLE); |
| 122 | + } |
| 123 | + |
| 124 | + // Theora version |
| 125 | + // Seems overly strict but this is what the spec says |
| 126 | + // VREV is for backwards-compatible changes, apparently |
| 127 | + if ( $h['VMAJ'] != 3 || $h['VMIN'] != 2 ) { |
| 128 | + throw new PEAR_Exception("Stream is undecodable due to an invalid theora version.", OGG_ERROR_UNDECODABLE); |
| 129 | + } |
| 130 | + $this->_theoraVersion = "{$h['VMAJ']}.{$h['VMIN']}.{$h['VREV']}"; |
| 131 | + |
| 132 | + // Frame height/width |
| 133 | + if ( !$h['FMBW'] || !$h['FMBH'] ) { |
| 134 | + throw new PEAR_Exception("Stream is undecodable because it has frame size of zero.", OGG_ERROR_UNDECODABLE); |
| 135 | + } |
| 136 | + $this->_frameWidth = $h['FMBW'] * 16; |
| 137 | + $this->_frameHeight = $h['FMBH'] * 16; |
| 138 | + |
| 139 | + // Picture height/width |
| 140 | + if ( $h['PICW'] > $this->_frameWidth || $h['PICH'] > $this->_frameHeight ) { |
| 141 | + throw new PEAR_Exception("Stream is undecodable because the picture width is greater than the frame width.", OGG_ERROR_UNDECODABLE); |
| 142 | + } |
| 143 | + $this->_pictureWidth = $h['PICW']; |
| 144 | + $this->_pictureHeight = $h['PICH']; |
| 145 | + |
| 146 | + // Picture offset |
| 147 | + $this->_offsetX = $h['PICX']; |
| 148 | + $this->_offsetY = $h['PICY']; |
| 149 | + // Frame rate |
| 150 | + $this->_frameRate = $h['FRD'] == 0 ? 0 : $h['FRN'] / $h['FRD']; |
| 151 | + // Physical aspect ratio |
| 152 | + if ( !$h['PARN'] || !$h['PARD'] ) { |
| 153 | + $this->_physicalAspectRatio = 1; |
| 154 | + } else { |
| 155 | + $this->_physicalAspectRatio = $h['PARN'] / $h['PARD']; |
| 156 | + } |
| 157 | + |
| 158 | + // Color space |
| 159 | + $colorSpaces = array( |
| 160 | + 0 => 'Undefined', |
| 161 | + 1 => 'Rec. 470M', |
| 162 | + 2 => 'Rec. 470BG', |
| 163 | + ); |
| 164 | + if ( isset( $colorSpaces[$h['CS']] ) ) { |
| 165 | + $this->_colorSpace = $colorSpaces[$h['CS']]; |
| 166 | + } else { |
| 167 | + $this->_colorSpace = 'Unknown (reserved)'; |
| 168 | + } |
| 169 | + |
| 170 | + $this->_nomBitrate = $h['NOMBR']; |
| 171 | + |
| 172 | + $this->_quality = $h['QUAL']; |
| 173 | + $this->_kfgShift = $h['KFGSHIFT']; |
| 174 | + |
| 175 | + $pixelFormats = array( |
| 176 | + 0 => '4:2:0', |
| 177 | + 1 => 'Unknown (reserved)', |
| 178 | + 2 => '4:2:2', |
| 179 | + 3 => '4:4:4', |
| 180 | + ); |
| 181 | + $this->_pixelFormat = $pixelFormats[$h['PF']]; |
| 182 | + |
| 183 | + switch ( $h['PF'] ) { |
| 184 | + case 0: |
| 185 | + $h['NSBS'] = |
| 186 | + floor( ($h['FMBW'] + 1) / 2 ) * |
| 187 | + floor( ($h['FMBH'] + 1) / 2 ) + 2 * |
| 188 | + floor( ($h['FMBW'] + 3) / 4 ) * |
| 189 | + floor( ($h['FMBH'] + 3) / 4 ); |
| 190 | + $h['NBS'] = 6 * $h['FMBW'] * $h['FMBH']; |
| 191 | + break; |
| 192 | + case 2: |
| 193 | + $h['NSBS'] = |
| 194 | + floor( ($h['FMBW'] + 1) / 2 ) * |
| 195 | + floor( ($h['FMBH'] + 1) / 2 ) + 2 * |
| 196 | + floor( ($h['FMBW'] + 3) / 4 ) * |
| 197 | + floor( ($h['FMBH'] + 1) / 2 ); |
| 198 | + $h['NBS'] = 8 * $h['FMBW'] * $h['FMBH']; |
| 199 | + break; |
| 200 | + case 3: |
| 201 | + $h['NSBS'] = |
| 202 | + 3 * floor( ($h['FMBW'] + 1) / 2 ) * |
| 203 | + floor( ($h['FMBH'] + 1) / 2 ); |
| 204 | + $h['NBS'] = 12 * $h['FMBW'] * $h['FMBH']; |
| 205 | + break; |
| 206 | + default: |
| 207 | + $h['NSBS'] = $h['NBS'] = 0; |
| 208 | + } |
| 209 | + $h['NMBS'] = $h['FMBW'] * $h['FMBH']; |
| 210 | + |
| 211 | + $this->_idHeader = $h; |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Get an associative array containing header information about the stream |
| 216 | + * @access public |
| 217 | + * @return array |
| 218 | + */ |
| 219 | + function getHeader() { |
| 220 | + return $this->_idHeader; |
| 221 | + } |
| 222 | + |
| 223 | + /** |
| 224 | + * Get a short string describing the type of the stream |
| 225 | + * @return string |
| 226 | + */ |
| 227 | + function getType() { |
| 228 | + return 'Theora'; |
| 229 | + } |
| 230 | + |
| 231 | + /** |
| 232 | + * Decode the comments header |
| 233 | + * @access private |
| 234 | + */ |
| 235 | + function _decodeCommentsHeader() |
| 236 | + { |
| 237 | + $this->_decodeCommonHeader(OGG_THEORA_COMMENTS_HEADER, OGG_THEORA_COMMENTS_PAGE_OFFSET); |
| 238 | + $this->_decodeBareCommentsHeader(); |
| 239 | + } |
| 240 | + |
| 241 | +} |
| 242 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Theora.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 243 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Speex.php |
— | — | @@ -0,0 +1,123 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +require_once('File/Ogg/Media.php'); |
| 26 | + |
| 27 | +/** |
| 28 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 29 | + * @category File |
| 30 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 31 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 32 | + * @link http://pear.php.net/package/File_Ogg |
| 33 | + * @link http://www.speex.org/docs.html |
| 34 | + * @package File_Ogg |
| 35 | + * @version CVS: $Id: Speex.php,v 1.10 2005/11/16 20:43:27 djg Exp $ |
| 36 | + */ |
| 37 | +class File_Ogg_Speex extends File_Ogg_Media |
| 38 | +{ |
| 39 | + /** |
| 40 | + * @access private |
| 41 | + */ |
| 42 | + function __construct($streamSerial, $streamData, $filePointer) |
| 43 | + { |
| 44 | + parent::__construct($streamSerial, $streamData, $filePointer); |
| 45 | + $this->_decodeHeader(); |
| 46 | + $this->_decodeCommentsHeader(); |
| 47 | + $endSec = |
| 48 | + (( '0x' . substr( $this->_lastGranulePos, 0, 8 ) ) * pow(2, 32) |
| 49 | + + ( '0x' . substr( $this->_lastGranulePos, 8, 8 ) )) |
| 50 | + / $this->_header['rate']; |
| 51 | + |
| 52 | + $startSec = |
| 53 | + (( '0x' . substr( $this->_firstGranulePos, 0, 8 ) ) * pow(2, 32) |
| 54 | + + ( '0x' . substr( $this->_firstGranulePos, 8, 8 ) )) |
| 55 | + / $this->_header['rate']; |
| 56 | + |
| 57 | + //make sure the offset is worth taking into account oggz_chop related hack |
| 58 | + if( $startSec > 1){ |
| 59 | + $this->_streamLength = $endSec - $startSec; |
| 60 | + $this->_startOffset = $startSec; |
| 61 | + }else{ |
| 62 | + $this->_streamLength = $endSec; |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + /** |
| 67 | + * Get a short string describing the type of the stream |
| 68 | + * @return string |
| 69 | + */ |
| 70 | + function getType() |
| 71 | + { |
| 72 | + return 'Speex'; |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * Decode the stream header |
| 77 | + * @access private |
| 78 | + */ |
| 79 | + function _decodeHeader() |
| 80 | + { |
| 81 | + fseek($this->_filePointer, $this->_streamData['pages'][0]['body_offset'], SEEK_SET); |
| 82 | + // The first 8 characters should be "Speex ". |
| 83 | + if (fread($this->_filePointer, 8) != 'Speex ') |
| 84 | + throw new PEAR_Exception("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); |
| 85 | + |
| 86 | + $this->_version = fread($this->_filePointer, 20); |
| 87 | + $this->_header = File_Ogg::_readLittleEndian($this->_filePointer, array( |
| 88 | + 'speex_version_id' => 32, |
| 89 | + 'header_size' => 32, |
| 90 | + 'rate' => 32, |
| 91 | + 'mode' => 32, |
| 92 | + 'mode_bitstream_version'=> 32, |
| 93 | + 'nb_channels' => 32, |
| 94 | + 'bitrate' => 32, |
| 95 | + 'frame_size' => 32, |
| 96 | + 'vbr' => 32, |
| 97 | + 'frames_per_packet' => 32, |
| 98 | + 'extra_headers' => 32, |
| 99 | + 'reserved1' => 32, |
| 100 | + 'reserved2' => 32 |
| 101 | + )); |
| 102 | + $this->_header['speex_version'] = $this->_version; |
| 103 | + } |
| 104 | + |
| 105 | + /** |
| 106 | + * Get an associative array containing header information about the stream |
| 107 | + * @access public |
| 108 | + * @return array |
| 109 | + */ |
| 110 | + function getHeader() { |
| 111 | + return $this->_header; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Decode the comments header |
| 116 | + * @access private |
| 117 | + */ |
| 118 | + function _decodeCommentsHeader() |
| 119 | + { |
| 120 | + fseek($this->_filePointer, $this->_streamData['pages'][1]['body_offset'], SEEK_SET); |
| 121 | + $this->_decodeBareCommentsHeader(); |
| 122 | + } |
| 123 | +} |
| 124 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Speex.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 125 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Vorbis.php |
— | — | @@ -0,0 +1,808 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +require_once('File/Ogg/Bitstream.php'); |
| 26 | + |
| 27 | +/** |
| 28 | + * Check number for the first header in a Vorbis stream. |
| 29 | + * |
| 30 | + * @access private |
| 31 | + */ |
| 32 | +define("OGG_VORBIS_IDENTIFICATION_HEADER", 1); |
| 33 | +/** |
| 34 | + * Check number for the second header in a Vorbis stream. |
| 35 | + * |
| 36 | + * @access private |
| 37 | + */ |
| 38 | +define("OGG_VORBIS_COMMENTS_HEADER", 3); |
| 39 | +/** |
| 40 | + * Check number for the third header in a Vorbis stream. |
| 41 | + * |
| 42 | + * @access private |
| 43 | + */ |
| 44 | +define("OGG_VORBIS_SETUP_HEADER", 5); |
| 45 | +/** |
| 46 | + * Error thrown if the stream appears to be corrupted. |
| 47 | + * |
| 48 | + * @access private |
| 49 | + */ |
| 50 | +define("OGG_VORBIS_ERROR_UNDECODABLE", OGG_ERROR_UNDECODABLE); |
| 51 | +/** |
| 52 | + * Error thrown if the user attempts to extract a comment using a comment key |
| 53 | + * that does not exist. |
| 54 | + * |
| 55 | + * @access private |
| 56 | + */ |
| 57 | +define("OGG_VORBIS_ERROR_INVALID_COMMENT", 2); |
| 58 | + |
| 59 | +define("OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET", 0); |
| 60 | +define("OGG_VORBIS_COMMENTS_PAGE_OFFSET", 1); |
| 61 | + |
| 62 | +/** |
| 63 | + * Error thrown if the user attempts to write a comment containing an illegal |
| 64 | + * character |
| 65 | + * |
| 66 | + * @access private |
| 67 | + */ |
| 68 | +define("OGG_VORBIS_ERROR_ILLEGAL_COMMENT", 3); |
| 69 | + |
| 70 | +/** |
| 71 | + * Extract the contents of a Vorbis logical stream. |
| 72 | + * |
| 73 | + * This class provides an interface to a Vorbis logical stream found within |
| 74 | + * a Ogg stream. A variety of information may be extracted, including comment |
| 75 | + * tags, running time, and bitrate. For more information, please see the following |
| 76 | + * links. |
| 77 | + * |
| 78 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 79 | + * @category File |
| 80 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 81 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 82 | + * @link http://pear.php.net/package/File_Ogg |
| 83 | + * @link http://www.xiph.org/vorbis/doc/ |
| 84 | + * @package File_Ogg |
| 85 | + * @version CVS: $Id: Vorbis.php,v 1.13 2005/11/19 09:06:32 djg Exp $ |
| 86 | + */ |
| 87 | +class File_Ogg_Vorbis extends File_Ogg_Media |
| 88 | +{ |
| 89 | + |
| 90 | + /** |
| 91 | + * Version of vorbis specification used. |
| 92 | + * |
| 93 | + * @access private |
| 94 | + * @var int |
| 95 | + */ |
| 96 | + var $_version; |
| 97 | + |
| 98 | + /** |
| 99 | + * Number of channels in the vorbis stream. |
| 100 | + * |
| 101 | + * @access private |
| 102 | + * @var int |
| 103 | + */ |
| 104 | + var $_channels; |
| 105 | + |
| 106 | + /** |
| 107 | + * Number of samples per second in the vorbis stream. |
| 108 | + * |
| 109 | + * @access private |
| 110 | + * @var int |
| 111 | + */ |
| 112 | + var $_sampleRate; |
| 113 | + |
| 114 | + /** |
| 115 | + * Minimum bitrate for the vorbis stream. |
| 116 | + * |
| 117 | + * @access private |
| 118 | + * @var int |
| 119 | + */ |
| 120 | + var $_minBitrate; |
| 121 | + |
| 122 | + /** |
| 123 | + * Maximum bitrate for the vorbis stream. |
| 124 | + * |
| 125 | + * @access private |
| 126 | + * @var int |
| 127 | + */ |
| 128 | + var $_maxBitrate; |
| 129 | + |
| 130 | + /** |
| 131 | + * Nominal bitrate for the vorbis stream. |
| 132 | + * |
| 133 | + * @access private |
| 134 | + * @var int |
| 135 | + */ |
| 136 | + var $_nomBitrate; |
| 137 | + |
| 138 | + /** |
| 139 | + * Average bitrate for the vorbis stream. |
| 140 | + * |
| 141 | + * @access private |
| 142 | + * @var float |
| 143 | + */ |
| 144 | + var $_avgBitrate; |
| 145 | + |
| 146 | + /** |
| 147 | + * The length of this stream in seconds. |
| 148 | + * |
| 149 | + * @access private |
| 150 | + * @var int |
| 151 | + */ |
| 152 | + var $_streamLength; |
| 153 | + |
| 154 | + /** |
| 155 | + * the start offset of this stream in seconds |
| 156 | + */ |
| 157 | + var $_startOffset; |
| 158 | + /** |
| 159 | + * Constructor for accessing a Vorbis logical stream. |
| 160 | + * |
| 161 | + * This method is the constructor for the native-PHP interface to a Vorbis logical |
| 162 | + * stream, embedded within an Ogg physical stream. |
| 163 | + * |
| 164 | + * @param int $streamSerial Serial number of the logical stream. |
| 165 | + * @param array $streamData Data for the requested logical stream. |
| 166 | + * @param string $filePath Location of a file on the filesystem. |
| 167 | + * @param pointer $filePointer File pointer for the current physical stream. |
| 168 | + * @access private |
| 169 | + */ |
| 170 | + function __construct($streamSerial, $streamData, $filePointer) |
| 171 | + { |
| 172 | + parent::__construct($streamSerial, $streamData, $filePointer); |
| 173 | + $this->_decodeIdentificationHeader(); |
| 174 | + $this->_decodeCommentsHeader(OGG_VORBIS_COMMENTS_HEADER, OGG_VORBIS_COMMENTS_PAGE_OFFSET); |
| 175 | + |
| 176 | + $endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos ); |
| 177 | + $startSec = $this->getSecondsFromGranulePos( $this->_firstGranulePos ); |
| 178 | + |
| 179 | + //make sure the offset is worth taking into account oggz_chop related hack |
| 180 | + if( $startSec > 1){ |
| 181 | + $this->_streamLength = $endSec - $startSec; |
| 182 | + $this->_startOffset = $startSec; |
| 183 | + }else{ |
| 184 | + $this->_streamLength = $endSec; |
| 185 | + } |
| 186 | + |
| 187 | + $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0; |
| 188 | + } |
| 189 | + function getSecondsFromGranulePos( $granulePos ){ |
| 190 | + return (( '0x' . substr( $granulePos, 0, 8 ) ) * pow(2, 32) |
| 191 | + + ( '0x' . substr( $granulePos, 8, 8 ) )) |
| 192 | + / $this->_idHeader['audio_sample_rate']; |
| 193 | + } |
| 194 | + /** |
| 195 | + * Get a short string describing the type of the stream |
| 196 | + */ |
| 197 | + function getType() |
| 198 | + { |
| 199 | + return 'Vorbis'; |
| 200 | + } |
| 201 | + |
| 202 | + /** |
| 203 | + * Parse the identification header (the first of three headers) in a Vorbis stream. |
| 204 | + * |
| 205 | + * This function parses the identification header. The identification header |
| 206 | + * contains simple audio characteristics, such as sample rate and number of |
| 207 | + * channels. There are a number of error-checking provisions laid down in the Vorbis |
| 208 | + * specification to ensure the stream is pure. |
| 209 | + * |
| 210 | + * @access private |
| 211 | + */ |
| 212 | + function _decodeIdentificationHeader() |
| 213 | + { |
| 214 | + $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET); |
| 215 | + |
| 216 | + $h = File_Ogg::_readLittleEndian($this->_filePointer, array( |
| 217 | + 'vorbis_version' => 32, |
| 218 | + 'audio_channels' => 8, |
| 219 | + 'audio_sample_rate' => 32, |
| 220 | + 'bitrate_maximum' => 32, |
| 221 | + 'bitrate_nominal' => 32, |
| 222 | + 'bitrate_minimum' => 32, |
| 223 | + 'blocksize_0' => 4, |
| 224 | + 'blocksize_1' => 4, |
| 225 | + 'framing_flag' => 1 |
| 226 | + )); |
| 227 | + |
| 228 | + // The Vorbis stream version must be 0. |
| 229 | + if ($h['vorbis_version'] == 0) |
| 230 | + $this->_version = $h['vorbis_version']; |
| 231 | + else |
| 232 | + throw new PEAR_Exception("Stream is undecodable due to an invalid vorbis stream version.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 233 | + |
| 234 | + // The number of channels MUST be greater than 0. |
| 235 | + if ($h['audio_channels'] == 0) |
| 236 | + throw new PEAR_Exception("Stream is undecodable due to zero channels.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 237 | + else |
| 238 | + $this->_channels = $h['audio_channels']; |
| 239 | + |
| 240 | + // The sample rate MUST be greater than 0. |
| 241 | + if ($h['audio_sample_rate'] == 0) |
| 242 | + throw new PEAR_Exception("Stream is undecodable due to a zero sample rate.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 243 | + else |
| 244 | + $this->_sampleRate = $h['audio_sample_rate']; |
| 245 | + |
| 246 | + // Extract the various bitrates |
| 247 | + $this->_maxBitrate = $h['bitrate_maximum']; |
| 248 | + $this->_nomBitrate = $h['bitrate_nominal']; |
| 249 | + $this->_minBitrate = $h['bitrate_minimum']; |
| 250 | + |
| 251 | + // Powers of two between 6 and 13 inclusive. |
| 252 | + $valid_block_sizes = array(64, 128, 256, 512, 1024, 2048, 4096, 8192); |
| 253 | + |
| 254 | + // blocksize_0 MUST be a valid blocksize. |
| 255 | + $blocksize_0 = pow(2, $h['blocksize_0']); |
| 256 | + if (FALSE == in_array($blocksize_0, $valid_block_sizes)) |
| 257 | + throw new PEAR_Exception("Stream is undecodable because blocksize_0 is $blocksize_0, which is not a valid size.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 258 | + |
| 259 | + // Extract bits 5 to 8 from the character data. |
| 260 | + // blocksize_1 MUST be a valid blocksize. |
| 261 | + $blocksize_1 = pow(2, $h['blocksize_1']); |
| 262 | + if (FALSE == in_array($blocksize_1, $valid_block_sizes)) |
| 263 | + throw new PEAR_Exception("Stream is undecodable because blocksize_1 is not a valid size.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 264 | + |
| 265 | + // blocksize 0 MUST be less than or equal to blocksize 1. |
| 266 | + if ($blocksize_0 > $blocksize_1) |
| 267 | + throw new PEAR_Exception("Stream is undecodable because blocksize_0 is not less than or equal to blocksize_1.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 268 | + |
| 269 | + // The framing bit MUST be set to mark the end of the identification header. |
| 270 | + // Some encoders are broken though -- TS |
| 271 | + /* |
| 272 | + if ($h['framing_flag'] == 0) |
| 273 | + throw new PEAR_Exception("Stream in undecodable because the framing bit is not non-zero.", OGG_VORBIS_ERROR_UNDECODABLE); |
| 274 | + */ |
| 275 | + |
| 276 | + $this->_idHeader = $h; |
| 277 | + } |
| 278 | + |
| 279 | + /** |
| 280 | + * Decode the comments header |
| 281 | + * @access private |
| 282 | + * @param int $packetType |
| 283 | + * @param int $pageOffset |
| 284 | + */ |
| 285 | + function _decodeCommentsHeader($packetType, $pageOffset) |
| 286 | + { |
| 287 | + $this->_decodeCommonHeader($packetType, $pageOffset); |
| 288 | + $this->_decodeBareCommentsHeader(); |
| 289 | + // The framing bit MUST be set to mark the end of the comments header. |
| 290 | + $framing_bit = unpack("Cdata", fread($this->_filePointer, 1)); |
| 291 | + if ($framing_bit['data'] != 1) |
| 292 | + throw new PEAR_Exception("Stream Undecodable", OGG_VORBIS_ERROR_UNDECODABLE); |
| 293 | + } |
| 294 | + |
| 295 | + /** |
| 296 | + * Get the 6-byte identification string expected in the common header |
| 297 | + */ |
| 298 | + function getIdentificationString() { |
| 299 | + return OGG_STREAM_CAPTURE_VORBIS; |
| 300 | + } |
| 301 | + |
| 302 | + /** |
| 303 | + * Version of the Vorbis specification referred to in the encoding of this stream. |
| 304 | + * |
| 305 | + * This method returns the version of the Vorbis specification (currently 0 (ZERO)) |
| 306 | + * referred to by the encoder of this stream. The Vorbis specification is well- |
| 307 | + * defined, and thus one does not expect this value to change on a frequent basis. |
| 308 | + * |
| 309 | + * @access public |
| 310 | + * @return int |
| 311 | + */ |
| 312 | + function getEncoderVersion() |
| 313 | + { |
| 314 | + return ($this->_version); |
| 315 | + } |
| 316 | + |
| 317 | + /** |
| 318 | + * Number of channels used in this stream |
| 319 | + * |
| 320 | + * This function returns the number of channels used in this stream. This |
| 321 | + * can range from 1 to 255, but will likely be 2 (stereo) or 1 (mono). |
| 322 | + * |
| 323 | + * @access public |
| 324 | + * @return int |
| 325 | + * @see File_Ogg_Vorbis::isMono() |
| 326 | + * @see File_Ogg_Vorbis::isStereo() |
| 327 | + * @see File_Ogg_Vorbis::isQuadrophonic() |
| 328 | + */ |
| 329 | + function getChannels() |
| 330 | + { |
| 331 | + return ($this->_channels); |
| 332 | + } |
| 333 | + |
| 334 | + /** |
| 335 | + * Samples per second. |
| 336 | + * |
| 337 | + * This function returns the number of samples used per second in this |
| 338 | + * recording. Probably the most common value here is 44,100. |
| 339 | + * |
| 340 | + * @return int |
| 341 | + * @access public |
| 342 | + */ |
| 343 | + function getSampleRate() |
| 344 | + { |
| 345 | + return ($this->_sampleRate); |
| 346 | + } |
| 347 | + |
| 348 | + /** |
| 349 | + * Various bitrate measurements |
| 350 | + * |
| 351 | + * Gives an array of the values of four different types of bitrates for this |
| 352 | + * stream. The nominal, maximum and minimum values are found within the file, |
| 353 | + * whereas the average value is computed. |
| 354 | + * |
| 355 | + * @access public |
| 356 | + * @return array |
| 357 | + */ |
| 358 | + function getBitrates() |
| 359 | + { |
| 360 | + return (array("nom" => $this->_nomBitrate, "max" => $this->_maxBitrate, "min" => $this->_minBitrate, "avg" => $this->_avgBitrate)); |
| 361 | + } |
| 362 | + |
| 363 | + /** |
| 364 | + * Gives the most accurate bitrate measurement from this stream. |
| 365 | + * |
| 366 | + * This function returns the most accurate bitrate measurement for this |
| 367 | + * recording, depending on values set in the stream header. |
| 368 | + * |
| 369 | + * @access public |
| 370 | + * @return float |
| 371 | + */ |
| 372 | + function getBitrate() |
| 373 | + { |
| 374 | + if ($this->_avgBitrate != 0) |
| 375 | + return ($this->_avgBitrate); |
| 376 | + elseif ($this->_nomBitrate != 0) |
| 377 | + return ($this->_nomBitrate); |
| 378 | + else |
| 379 | + return (($this->_minBitrate + $this->_maxBitrate) / 2); |
| 380 | + } |
| 381 | + |
| 382 | + /** |
| 383 | + * Gives the length (in seconds) of this stream. |
| 384 | + * |
| 385 | + * @access public |
| 386 | + * @return int |
| 387 | + */ |
| 388 | + function getLength() |
| 389 | + { |
| 390 | + return ($this->_streamLength); |
| 391 | + } |
| 392 | + /** |
| 393 | + * Get the start offset of the stream in seconds |
| 394 | + * @access public |
| 395 | + * @return int |
| 396 | + */ |
| 397 | + function getStartOffset(){ |
| 398 | + return ($this->_startOffset); |
| 399 | + } |
| 400 | + /** |
| 401 | + * States whether this logical stream was encoded in mono. |
| 402 | + * |
| 403 | + * @access public |
| 404 | + * @return boolean |
| 405 | + */ |
| 406 | + function isMono() |
| 407 | + { |
| 408 | + return ($this->_channels == 1); |
| 409 | + } |
| 410 | + |
| 411 | + /** |
| 412 | + * States whether this logical stream was encoded in stereo. |
| 413 | + * |
| 414 | + * @access public |
| 415 | + * @return boolean |
| 416 | + */ |
| 417 | + function isStereo() |
| 418 | + { |
| 419 | + return ($this->_channels == 2); |
| 420 | + } |
| 421 | + |
| 422 | + /** |
| 423 | + * States whether this logical stream was encoded in quadrophonic sound. |
| 424 | + * |
| 425 | + * @access public |
| 426 | + * @return boolean |
| 427 | + */ |
| 428 | + function isQuadrophonic() |
| 429 | + { |
| 430 | + return ($this->_channels == 4); |
| 431 | + } |
| 432 | + |
| 433 | + /** |
| 434 | + * The title of this track, e.g. "What's Up Pussycat?". |
| 435 | + * |
| 436 | + * @access public |
| 437 | + * @return string |
| 438 | + */ |
| 439 | + function getTitle() |
| 440 | + { |
| 441 | + return ($this->getField("TITLE")); |
| 442 | + } |
| 443 | + |
| 444 | + /** |
| 445 | + * Set the title of this track. |
| 446 | + * |
| 447 | + * @access public |
| 448 | + * @param string $title |
| 449 | + * @param boolean $replace |
| 450 | + */ |
| 451 | + function setTitle($title, $replace = true) |
| 452 | + { |
| 453 | + $this->setField("TITLE", $title, $replace); |
| 454 | + } |
| 455 | + |
| 456 | + /** |
| 457 | + * The version of the track, such as a remix. |
| 458 | + * |
| 459 | + * @access public |
| 460 | + * @return string |
| 461 | + */ |
| 462 | + function getVersion() |
| 463 | + { |
| 464 | + return $this->getField("VERSION"); |
| 465 | + } |
| 466 | + |
| 467 | + /** |
| 468 | + * Set the version of this track. |
| 469 | + * |
| 470 | + * @access public |
| 471 | + * @param string $version |
| 472 | + * @param boolean $replace |
| 473 | + */ |
| 474 | + function setVersion($version, $replace = true) |
| 475 | + { |
| 476 | + $this->setField("VERSION", $version, $replace); |
| 477 | + } |
| 478 | + |
| 479 | + /** |
| 480 | + * The album or collection from which this track comes. |
| 481 | + * |
| 482 | + * @access public |
| 483 | + * @return string |
| 484 | + */ |
| 485 | + function getAlbum() |
| 486 | + { |
| 487 | + return ($this->getField("ALBUM")); |
| 488 | + } |
| 489 | + |
| 490 | + /** |
| 491 | + * Set the album or collection for this track. |
| 492 | + * |
| 493 | + * @access public |
| 494 | + * @param string $album |
| 495 | + * @param boolean $replace |
| 496 | + */ |
| 497 | + function setAlbum($album, $replace = true) |
| 498 | + { |
| 499 | + $this->setField("ALBUM", $album, $replace); |
| 500 | + } |
| 501 | + |
| 502 | + /** |
| 503 | + * The number of this track if it is part of a larger collection. |
| 504 | + * |
| 505 | + * @access public |
| 506 | + * @return string |
| 507 | + */ |
| 508 | + function getTrackNumber() |
| 509 | + { |
| 510 | + return ($this->getField("TRACKNUMBER")); |
| 511 | + } |
| 512 | + |
| 513 | + /** |
| 514 | + * Set the number of this relative to the collection. |
| 515 | + * |
| 516 | + * @access public |
| 517 | + * @param int $number |
| 518 | + * @param boolean $replace |
| 519 | + */ |
| 520 | + function setTrackNumber($number, $replace = true) |
| 521 | + { |
| 522 | + $this->setField("TRACKNUMBER", $number, $replace); |
| 523 | + } |
| 524 | + |
| 525 | + /** |
| 526 | + * The artist responsible for this track. |
| 527 | + * |
| 528 | + * This function returns the name of the artist responsible for this |
| 529 | + * recording, which may be either a solo-artist, duet or group. |
| 530 | + * |
| 531 | + * @access public |
| 532 | + * @return string |
| 533 | + */ |
| 534 | + function getArtist() |
| 535 | + { |
| 536 | + return ($this->getField("ARTIST")); |
| 537 | + } |
| 538 | + |
| 539 | + /** |
| 540 | + * Set the artist of this track. |
| 541 | + * |
| 542 | + * @access public |
| 543 | + * @param string $artist |
| 544 | + * @param boolean $replace |
| 545 | + */ |
| 546 | + function setArtist($artist, $replace = true) |
| 547 | + { |
| 548 | + $this->setField("ARTIST", $artist, $replace = true); |
| 549 | + } |
| 550 | + |
| 551 | + /** |
| 552 | + * The performer of this track, such as an orchestra |
| 553 | + * |
| 554 | + * @access public |
| 555 | + * @return string |
| 556 | + */ |
| 557 | + function getPerformer() |
| 558 | + { |
| 559 | + return ($this->getField("PERFORMER")); |
| 560 | + } |
| 561 | + |
| 562 | + /** |
| 563 | + * Set the performer of this track. |
| 564 | + * |
| 565 | + * @access public |
| 566 | + * @param string $performer |
| 567 | + * @param boolean $replace |
| 568 | + */ |
| 569 | + function setPerformer($performer, $replace = true) |
| 570 | + { |
| 571 | + $this->setField("PERFORMER", $performer, $replace); |
| 572 | + } |
| 573 | + |
| 574 | + /** |
| 575 | + * The copyright attribution for this track. |
| 576 | + * |
| 577 | + * @access public |
| 578 | + * @return string |
| 579 | + */ |
| 580 | + function getCopyright() |
| 581 | + { |
| 582 | + return ($this->getField("COPYRIGHT")); |
| 583 | + } |
| 584 | + |
| 585 | + /** |
| 586 | + * Set the copyright attribution for this track. |
| 587 | + * |
| 588 | + * @access public |
| 589 | + * @param string $copyright |
| 590 | + * @param boolean $replace |
| 591 | + */ |
| 592 | + function setCopyright($copyright, $replace = true) |
| 593 | + { |
| 594 | + $this->setField("COPYRIGHT", $copyright, $replace); |
| 595 | + } |
| 596 | + |
| 597 | + /** |
| 598 | + * The rights of distribution for this track. |
| 599 | + * |
| 600 | + * This funtion returns the license for this track, and may include |
| 601 | + * copyright information, or a creative commons statement. |
| 602 | + * |
| 603 | + * @access public |
| 604 | + * @return string |
| 605 | + */ |
| 606 | + function getLicense() |
| 607 | + { |
| 608 | + return ($this->getField("LICENSE")); |
| 609 | + } |
| 610 | + |
| 611 | + /** |
| 612 | + * Set the distribution rights for this track. |
| 613 | + * |
| 614 | + * @access public |
| 615 | + * @param string $license |
| 616 | + * @param boolean $replace |
| 617 | + */ |
| 618 | + function setLicense($license, $replace = true) |
| 619 | + { |
| 620 | + $this->setField("LICENSE", $license, $replace); |
| 621 | + } |
| 622 | + |
| 623 | + /** |
| 624 | + * The organisation responsible for this track. |
| 625 | + * |
| 626 | + * This function returns the name of the organisation responsible for |
| 627 | + * the production of this track, such as the record label. |
| 628 | + * |
| 629 | + * @access public |
| 630 | + * @return string |
| 631 | + */ |
| 632 | + function getOrganization() |
| 633 | + { |
| 634 | + return ($this->getField("ORGANIZATION")); |
| 635 | + } |
| 636 | + |
| 637 | + /** |
| 638 | + * Set the organisation responsible for this track. |
| 639 | + * |
| 640 | + * @access public |
| 641 | + * @param string $organization |
| 642 | + * @param boolean $replace |
| 643 | + */ |
| 644 | + function setOrganziation($organization, $replace = true) |
| 645 | + { |
| 646 | + $this->setField("ORGANIZATION", $organization, $replace); |
| 647 | + } |
| 648 | + |
| 649 | + /** |
| 650 | + * A short description of the contents of this track. |
| 651 | + * |
| 652 | + * This function returns a short description of this track, which might |
| 653 | + * contain extra information that doesn't fit anywhere else. |
| 654 | + * |
| 655 | + * @access public |
| 656 | + * @return string |
| 657 | + */ |
| 658 | + function getDescription() |
| 659 | + { |
| 660 | + return ($this->getField("DESCRIPTION")); |
| 661 | + } |
| 662 | + |
| 663 | + /** |
| 664 | + * Set the description of this track. |
| 665 | + * |
| 666 | + * @access public |
| 667 | + * @param string $description |
| 668 | + * @param boolean $replace |
| 669 | + */ |
| 670 | + function setDescription($description, $replace = true) |
| 671 | + { |
| 672 | + $this->setField("DESCRIPTION", $replace); |
| 673 | + } |
| 674 | + |
| 675 | + /** |
| 676 | + * The genre of this recording (e.g. Rock) |
| 677 | + * |
| 678 | + * This function returns the genre of this recording. There are no pre- |
| 679 | + * defined genres, so this is completely up to the tagging software. |
| 680 | + * |
| 681 | + * @access public |
| 682 | + * @return string |
| 683 | + */ |
| 684 | + function getGenre() |
| 685 | + { |
| 686 | + return ($this->getField("GENRE")); |
| 687 | + } |
| 688 | + |
| 689 | + /** |
| 690 | + * Set the genre of this track. |
| 691 | + * |
| 692 | + * @access public |
| 693 | + * @param string $genre |
| 694 | + * @param boolean $replace |
| 695 | + */ |
| 696 | + function setGenre($genre, $replace = true) |
| 697 | + { |
| 698 | + $this->setField("GENRE", $genre, $replace); |
| 699 | + } |
| 700 | + |
| 701 | + /** |
| 702 | + * The date of the recording of this track. |
| 703 | + * |
| 704 | + * This function returns the date on which this recording was made. There |
| 705 | + * is no specification for the format of this date. |
| 706 | + * |
| 707 | + * @access public |
| 708 | + * @return string |
| 709 | + */ |
| 710 | + function getDate() |
| 711 | + { |
| 712 | + return ($this->getField("DATE")); |
| 713 | + } |
| 714 | + |
| 715 | + /** |
| 716 | + * Set the date of recording for this track. |
| 717 | + * |
| 718 | + * @access public |
| 719 | + * @param string $date |
| 720 | + * @param boolean $replace |
| 721 | + */ |
| 722 | + function setDate($date, $replace = true) |
| 723 | + { |
| 724 | + $this->setField("DATE", $date, $replace); |
| 725 | + } |
| 726 | + |
| 727 | + /** |
| 728 | + * Where this recording was made. |
| 729 | + * |
| 730 | + * This function returns where this recording was made, such as a recording |
| 731 | + * studio, or concert venue. |
| 732 | + * |
| 733 | + * @access public |
| 734 | + * @return string |
| 735 | + */ |
| 736 | + function getLocation() |
| 737 | + { |
| 738 | + return ($this->getField("LOCATION")); |
| 739 | + } |
| 740 | + |
| 741 | + /** |
| 742 | + * Set the location of the recording of this track. |
| 743 | + * |
| 744 | + * @access public |
| 745 | + * @param string $location |
| 746 | + * @param boolean $replace |
| 747 | + */ |
| 748 | + function setLocation($location, $replace = true) |
| 749 | + { |
| 750 | + $this->setField("LOCATION", $location, $replace); |
| 751 | + } |
| 752 | + |
| 753 | + /** |
| 754 | + * @access public |
| 755 | + * @return string |
| 756 | + */ |
| 757 | + function getContact() |
| 758 | + { |
| 759 | + return ($this->getField("CONTACT")); |
| 760 | + } |
| 761 | + |
| 762 | + /** |
| 763 | + * Set the contact information for this track. |
| 764 | + * |
| 765 | + * @access public |
| 766 | + * @param string $contact |
| 767 | + * @param boolean $replace |
| 768 | + */ |
| 769 | + function setContact($contact, $replace = true) |
| 770 | + { |
| 771 | + $this->setField("CONTACT", $contact, $replace); |
| 772 | + } |
| 773 | + |
| 774 | + /** |
| 775 | + * International Standard Recording Code. |
| 776 | + * |
| 777 | + * Returns the International Standard Recording Code. This code can be |
| 778 | + * validated using the Validate_ISPN package. |
| 779 | + * |
| 780 | + * @access public |
| 781 | + * @return string |
| 782 | + */ |
| 783 | + function getIsrc() |
| 784 | + { |
| 785 | + return ($this->getField("ISRC")); |
| 786 | + } |
| 787 | + |
| 788 | + /** |
| 789 | + * Set the ISRC for this track. |
| 790 | + * |
| 791 | + * @access public |
| 792 | + * @param string $isrc |
| 793 | + * @param boolean $replace |
| 794 | + */ |
| 795 | + function setIsrc($isrc, $replace = true) |
| 796 | + { |
| 797 | + $this->setField("ISRC", $isrc, $replace); |
| 798 | + } |
| 799 | + |
| 800 | + /** |
| 801 | + * Get an associative array containing header information about the stream |
| 802 | + * @access public |
| 803 | + * @return array |
| 804 | + */ |
| 805 | + function getHeader() { |
| 806 | + return $this->_idHeader; |
| 807 | + } |
| 808 | +} |
| 809 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Vorbis.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 810 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Flac.php |
— | — | @@ -0,0 +1,134 @@ |
| 2 | +<?php |
| 3 | +/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
| 4 | +// +----------------------------------------------------------------------------+ |
| 5 | +// | File_Ogg PEAR Package for Accessing Ogg Bitstreams | |
| 6 | +// | Copyright (c) 2005-2007 | |
| 7 | +// | David Grant <david@grant.org.uk> | |
| 8 | +// | Tim Starling <tstarling@wikimedia.org> | |
| 9 | +// +----------------------------------------------------------------------------+ |
| 10 | +// | This library is free software; you can redistribute it and/or | |
| 11 | +// | modify it under the terms of the GNU Lesser General Public | |
| 12 | +// | License as published by the Free Software Foundation; either | |
| 13 | +// | version 2.1 of the License, or (at your option) any later version. | |
| 14 | +// | | |
| 15 | +// | This library is distributed in the hope that it will be useful, | |
| 16 | +// | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 17 | +// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 18 | +// | Lesser General Public License for more details. | |
| 19 | +// | | |
| 20 | +// | You should have received a copy of the GNU Lesser General Public | |
| 21 | +// | License along with this library; if not, write to the Free Software | |
| 22 | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 23 | +// +----------------------------------------------------------------------------+ |
| 24 | + |
| 25 | +require_once('File/Ogg/Media.php'); |
| 26 | + |
| 27 | +/** |
| 28 | + * @author David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 29 | + * @category File |
| 30 | + * @copyright David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org> |
| 31 | + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL |
| 32 | + * @link http://pear.php.net/package/File_Ogg |
| 33 | + * @link http://flac.sourceforge.net/documentation.html |
| 34 | + * @package File_Ogg |
| 35 | + * @version CVS: $Id: Flac.php,v 1.9 2005/11/16 20:43:27 djg Exp $ |
| 36 | + */ |
| 37 | +class File_Ogg_Flac extends File_Ogg_Media |
| 38 | +{ |
| 39 | + /** |
| 40 | + * @access private |
| 41 | + */ |
| 42 | + function __construct($streamSerial, $streamData, $filePointer) |
| 43 | + { |
| 44 | + parent::__construct($streamSerial, $streamData, $filePointer); |
| 45 | + $this->_decodeHeader(); |
| 46 | + $this->_decodeCommentsHeader(); |
| 47 | + $this->_streamLength = $this->_streamInfo['total_samples'] |
| 48 | + / $this->_streamInfo['sample_rate']; |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * Get a short string describing the type of the stream |
| 53 | + * @return string |
| 54 | + */ |
| 55 | + function getType() { |
| 56 | + return 'FLAC'; |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * @access private |
| 61 | + * @param int $packetType |
| 62 | + * @param int $pageOffset |
| 63 | + */ |
| 64 | + function _decodeHeader() |
| 65 | + { |
| 66 | + fseek($this->_filePointer, $this->_streamData['pages'][0]['body_offset'], SEEK_SET); |
| 67 | + // Check if this is the correct header. |
| 68 | + $packet = unpack("Cdata", fread($this->_filePointer, 1)); |
| 69 | + if ($packet['data'] != 0x7f) |
| 70 | + throw new PEAR_Exception("Stream Undecodable", OGG_ERROR_UNDECODABLE); |
| 71 | + |
| 72 | + // The following four characters should be "FLAC". |
| 73 | + if (fread($this->_filePointer, 4) != 'FLAC') |
| 74 | + throw new PEAR_Exception("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); |
| 75 | + |
| 76 | + $version = unpack("Cmajor/Cminor", fread($this->_filePointer, 2)); |
| 77 | + $this->_version = "{$version['major']}.{$version['minor']}"; |
| 78 | + if ($version['major'] > 1) { |
| 79 | + throw new PEAR_Exception("Cannot decode a version {$version['major']} FLAC stream", OGG_ERROR_UNDECODABLE); |
| 80 | + } |
| 81 | + $h = File_Ogg::_readBigEndian( $this->_filePointer, |
| 82 | + array( |
| 83 | + // Ogg-specific |
| 84 | + 'num_headers' => 16, |
| 85 | + 'flac_native_sig' => 32, |
| 86 | + // METADATA_BLOCK_HEADER |
| 87 | + 'is_last' => 1, |
| 88 | + 'type' => 7, |
| 89 | + 'length' => 24, |
| 90 | + )); |
| 91 | + |
| 92 | + // METADATA_BLOCK_STREAMINFO |
| 93 | + // The variable names are canonical, and come from the FLAC source (format.h) |
| 94 | + $this->_streamInfo = File_Ogg::_readBigEndian( $this->_filePointer, |
| 95 | + array( |
| 96 | + 'min_blocksize' => 16, |
| 97 | + 'max_blocksize' => 16, |
| 98 | + 'min_framesize' => 24, |
| 99 | + 'max_framesize' => 24, |
| 100 | + 'sample_rate' => 20, |
| 101 | + 'channels' => 3, |
| 102 | + 'bits_per_sample' => 5, |
| 103 | + 'total_samples' => 36, |
| 104 | + )); |
| 105 | + $this->_streamInfo['md5sum'] = bin2hex(fread($this->_filePointer, 16)); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Get an associative array containing header information about the stream |
| 110 | + * @access public |
| 111 | + * @return array |
| 112 | + */ |
| 113 | + function getHeader() |
| 114 | + { |
| 115 | + return $this->_streamInfo; |
| 116 | + } |
| 117 | + |
| 118 | + function _decodeCommentsHeader() |
| 119 | + { |
| 120 | + fseek($this->_filePointer, $this->_streamData['pages'][1]['body_offset'], SEEK_SET); |
| 121 | + $blockHeader = File_Ogg::_readBigEndian( $this->_filePointer, |
| 122 | + array( |
| 123 | + 'last_block' => 1, |
| 124 | + 'block_type' => 7, |
| 125 | + 'length' => 24 |
| 126 | + ) |
| 127 | + ); |
| 128 | + if ($blockHeader['block_type'] != 4) { |
| 129 | + throw new PEAR_Exception("Stream Undecodable", OGG_ERROR_UNDECODABLE); |
| 130 | + } |
| 131 | + |
| 132 | + $this->_decodeBareCommentsHeader(); |
| 133 | + } |
| 134 | +} |
| 135 | +?> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/Ogg/Flac.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 136 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/README |
— | — | @@ -0,0 +1,4 @@ |
| 2 | +This was originally a fork of the File_Ogg package in PEAR. I (TS) have now taken |
| 3 | +over maintainership of that package, and have merged my work back to there, |
| 4 | +starting with version 0.3.0. |
| 5 | + |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/File/README |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 6 | + native |
Index: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/package.xml |
— | — | @@ -0,0 +1,149 @@ |
| 2 | +<?xml version="1.0" encoding="UTF-8"?> |
| 3 | +<package packagerversion="1.6.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> |
| 4 | + <name>File_Ogg</name> |
| 5 | + <channel>pear.php.net</channel> |
| 6 | + <summary>Retrieves metadata from Ogg files.</summary> |
| 7 | + <description>Decodes Ogg file headers to provide metadata. Supports Vorbis, |
| 8 | +Theora, Speex and FLAC stream headers. This package does |
| 9 | +NOT provide codecs for these formats. |
| 10 | + </description> |
| 11 | + <lead> |
| 12 | + <name>Tim Starling</name> |
| 13 | + <user>tstarling</user> |
| 14 | + <email>tstarling@wikimedia.org</email> |
| 15 | + <active>yes</active> |
| 16 | + </lead> |
| 17 | + <date>2008-01-31</date> |
| 18 | + <time>15:25:07</time> |
| 19 | + <version> |
| 20 | + <release>0.3.0</release> |
| 21 | + <api>0.3.0</api> |
| 22 | + </version> |
| 23 | + <stability> |
| 24 | + <release>beta</release> |
| 25 | + <api>beta</api> |
| 26 | + </stability> |
| 27 | + <license>GNU LGPL</license> |
| 28 | + <notes>* Change in maintainership |
| 29 | +* Added header parsing for Theora, Speex and Flac. |
| 30 | +* Removed partially written save/encode code. |
| 31 | +* Introduced some PHP 5 features, no longer supports PHP 4. |
| 32 | +* Introduced convenience functions getHeader() and getComments() to retrieve metadata in associative array format. |
| 33 | +* Fixed the error handling, using exceptions instead of inappropriate continuation. |
| 34 | +* Added overall length calculation for multiplexed streams |
| 35 | +* Fixed a page splitting bug |
| 36 | + |
| 37 | + </notes> |
| 38 | + <contents> |
| 39 | + <dir name="/"> |
| 40 | + <dir name="File"> |
| 41 | + <dir name="Ogg"> |
| 42 | + <file name="Bitstream.php" role="php" /> |
| 43 | + <file name="Flac.php" role="php" /> |
| 44 | + <file name="Media.php" role="php" /> |
| 45 | + <file name="Speex.php" role="php" /> |
| 46 | + <file name="Theora.php" role="php" /> |
| 47 | + <file name="Vorbis.php" role="php" /> |
| 48 | + </dir> <!-- //File/Ogg --> |
| 49 | + <file name="Ogg.php" role="php" /> |
| 50 | + </dir> <!-- //File --> |
| 51 | + </dir> <!-- / --> |
| 52 | + </contents> |
| 53 | + <dependencies> |
| 54 | + <required> |
| 55 | + <php> |
| 56 | + <min>5.0.0</min> |
| 57 | + </php> |
| 58 | + <pearinstaller> |
| 59 | + <min>1.4.0b1</min> |
| 60 | + </pearinstaller> |
| 61 | + </required> |
| 62 | + </dependencies> |
| 63 | + <phprelease /> |
| 64 | + <changelog> |
| 65 | + <release> |
| 66 | + <version> |
| 67 | + <release>0.2.1</release> |
| 68 | + <api>0.2.1</api> |
| 69 | + </version> |
| 70 | + <stability> |
| 71 | + <release>beta</release> |
| 72 | + <api>beta</api> |
| 73 | + </stability> |
| 74 | + <date>2005-11-03</date> |
| 75 | + <license>GNU LGPL</license> |
| 76 | + <notes>Fixed some basic bugs from the previous release. |
| 77 | + </notes> |
| 78 | + </release> |
| 79 | + <release> |
| 80 | + <version> |
| 81 | + <release>0.2.0</release> |
| 82 | + <api>0.2.0</api> |
| 83 | + </version> |
| 84 | + <stability> |
| 85 | + <release>beta</release> |
| 86 | + <api>beta</api> |
| 87 | + </stability> |
| 88 | + <date>2005-11-03</date> |
| 89 | + <license>GNU LGPL</license> |
| 90 | + <notes>Updated to beta release, changed license to LGPL. |
| 91 | + </notes> |
| 92 | + </release> |
| 93 | + <release> |
| 94 | + <version> |
| 95 | + <release>0.1.3</release> |
| 96 | + <api>0.1.3</api> |
| 97 | + </version> |
| 98 | + <stability> |
| 99 | + <release>alpha</release> |
| 100 | + <api>alpha</api> |
| 101 | + </stability> |
| 102 | + <date>2005-09-21</date> |
| 103 | + <license>GNU LGPL</license> |
| 104 | + <notes>Change to package.xml. |
| 105 | + </notes> |
| 106 | + </release> |
| 107 | + <release> |
| 108 | + <version> |
| 109 | + <release>0.1.2</release> |
| 110 | + <api>0.1.2</api> |
| 111 | + </version> |
| 112 | + <stability> |
| 113 | + <release>alpha</release> |
| 114 | + <api>alpha</api> |
| 115 | + </stability> |
| 116 | + <date>2003-09-09</date> |
| 117 | + <license>GNU LGPL</license> |
| 118 | + <notes>Extensive documentation for the majority of PHP elements. |
| 119 | + </notes> |
| 120 | + </release> |
| 121 | + <release> |
| 122 | + <version> |
| 123 | + <release>0.1.1</release> |
| 124 | + <api>0.1.1</api> |
| 125 | + </version> |
| 126 | + <stability> |
| 127 | + <release>alpha</release> |
| 128 | + <api>alpha</api> |
| 129 | + </stability> |
| 130 | + <date>2003-09-02</date> |
| 131 | + <license>GNU LGPL</license> |
| 132 | + <notes>Bugfix release: package was installing in the wrong location. |
| 133 | + </notes> |
| 134 | + </release> |
| 135 | + <release> |
| 136 | + <version> |
| 137 | + <release>0.1</release> |
| 138 | + <api>0.1</api> |
| 139 | + </version> |
| 140 | + <stability> |
| 141 | + <release>alpha</release> |
| 142 | + <api>alpha</api> |
| 143 | + </stability> |
| 144 | + <date>2003-09-02</date> |
| 145 | + <license>GNU LGPL</license> |
| 146 | + <notes>The first release! |
| 147 | + </notes> |
| 148 | + </release> |
| 149 | + </changelog> |
| 150 | +</package> |
Property changes on: trunk/extensions/TimedMediaHandler/handlers/OggHandler/PEAR/File_Ogg/package.xml |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 151 | + native |
Index: trunk/extensions/TimedMediaHandler/TimedMediaHandler.php |
— | — | @@ -32,15 +32,15 @@ |
33 | 33 | $wgAutoloadClasses['TimedMediaThumbnail'] = "$timedMediaDir/TimedMediaThumbnail.php"; |
34 | 34 | |
35 | 35 | // Ogg Handler |
36 | | -$wgAutoloadClasses['OggHandler'] = "$timedMediaDir/OggHandler/OggHandler.php"; |
| 36 | +$wgAutoloadClasses['OggHandler'] = "$timedMediaDir/handlers/OggHandler/OggHandler.php"; |
37 | 37 | ini_set( 'include_path', |
38 | | - "$timedMediaDir/OggHandler/PEAR/File_Ogg" . |
| 38 | + "$timedMediaDir/handlers/OggHandler/PEAR/File_Ogg" . |
39 | 39 | PATH_SEPARATOR . |
40 | 40 | ini_get( 'include_path' ) ); |
41 | 41 | |
42 | 42 | // WebM Handler |
43 | | -$wgAutoloadClasses['WebMHandler'] = "$timedMediaDir/WebMHandler/WebMHandler.php"; |
44 | | -$wgAutoloadClasses['getID3' ] = "$timedMediaDir/WebMHandler/getid3.php"; |
| 43 | +$wgAutoloadClasses['WebMHandler'] = "$timedMediaDir/handlers/WebMHandler/WebMHandler.php"; |
| 44 | +$wgAutoloadClasses['getID3' ] = "$timedMediaDir/handlers/WebMHandler/getid3.php"; |
45 | 45 | |
46 | 46 | $wgAutoloadClasses['WebVideoTranscode'] = "$timedMediaDir/WebVideoTranscode/WebVideoTranscode.php"; |
47 | 47 | $wgAutoloadClasses['WebVideoTranscodeJob'] = "$timedMediaDir/WebVideoTranscode/WebVideoTranscodeJob.php"; |