-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfiletype-mpeg4.php
More file actions
320 lines (271 loc) · 9.93 KB
/
filetype-mpeg4.php
File metadata and controls
320 lines (271 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
<?php
/**
* @file filetype-mpeg4.php
*
* Really simple mpeg4 parser that really only knows as much as it needs to know
* to be able to get metadata out of iTunes-created files.
* Thanks to Atomic Parsley, ISO, and getID3 for reference.
* Copyright (C) 2009 Josh Channings
*
* To use, run mp4_unpack($filename) on your target file.
* It'll return an array of tags in their original heirarchy, so you can access it like:-
* mp4_unpack("file.m4v")['moov']['udta']['meta']['ilst']['covr']['data'];
*
* It ignores free boxes, and containers it doesn't know about.
* It really only gives you information out of moov.udta.meta.ilst,
* but I might expand it for other purposes in the future. The infrastructure's
* in place, I just haven't told it what any of the other box types are.
*
*/
/**
* Convert a big-endian number to a PHP int
*
* @param $str String of bytes that make up the number
* @returns PHP-native representation of the number
*/
function mp4_bigend2int($str)
{
$n = 0;
$len = strlen($str);
for ($i = 0;$i < $len;$i++)
$n += ord($str{$i}) * pow(256, ($len - 1 - $i));
return (int) $n;
}
/**
* Reads the binary data of an MPEG4 box and puts it in a PHP Array
*
* This function does the majority of the work, it's the only function in this file that has
* knowledge of actual MPEG4 internals.
*
* @param $fd File descriptor we're working on
* @param $parent_box Array to put our box in
* @param $start Position of this box within the file
*
* @returns The seek point of the next box after the one it's reading
*/
function mp4_read_box($fd, &$parent_box, $start)
{
// Seek to the beginning of the box
fseek($fd, $start, SEEK_SET);
// Get the length of our box
$length = mp4_bigend2int(fread($fd, 4));
// This is the offset of the first byte of the next box (after our last byte)
$end = $start + $length;
// Read the fourcc into $name
$name = strtolower(fread($fd, 4));
// Make the array in the target structure
$parent_box[$name] = array();
// Put the contents of the box in here
$data = '';
/* This switch does most of the work in this function. All the knowledge of the
* soecific types of boxes and their names are in here.
*/
switch($name)
{
// This belongs with the other 'ilst' children, but we don't want the
// flag check to happen for all the other boxes, so it goes
// first in the switch. Deal with it.
case 'covr': // Album cover
fseek($fd, $start + 19, SEEK_SET); // Seek to the flag in question
switch(ord(fread($fd, 1))) // Read it and switch it
{
case 0x0d: $parent_box['covr']['filetype'] = "jpg"; break;
case 0x0e: $parent_box['covr']['filetype'] = "png"; break;
}
fseek($fd, $start, SEEK_SET); // Jump back to where we were...
// Container formats without flags
case 'moov': // Moooooovie (...or just movie)
case 'udta': // User-added data
case 'ilst': // iTunes metadata container
// iTunes tags (under ilst)
// These all store their data in a 'data' child box
case '©alb': // Album
case '©art': // Artist
case 'aart': // Album Artist
case '©cmt': // Comment
case '©day': // Year
case '©nam': // Title
case '©gen': // Genre
case 'gnre': // Genre
case 'trkn': // Track Number
case 'disk': // Disk Number
case '©wrt': // Composer
case '©too': // Encoder
case 'tmpo': // BPM
case 'cprt': // Copyright
case 'cpil': // Compilation
case 'rtng': // Rating/Advisory
case '©grp': // Grouping
case 'stik': // ??? - always comes after 'covr' AFAIK
case 'pcst': // Podcast
case 'catg': // Category
case 'keyw': // Keyword
case 'purl': // Podcast URL
case 'egid': // Episode Global Unique ID
case 'desc': // Description
case '©lyr': // Lyrics
case 'tvnn': // TV Network Name
case 'tvsh': // TV Show
case 'tven':
case 'tvsn':
case 'tves':
case 'purd': // Purchase Data
case 'pgap': // Gapless Playback
$i = $start + 8; // Keep going til we get to the end of this box
while($i < $end) $i = mp4_read_box($fd, $parent_box[$name], $i);
break;
// iTunes data format (4b length + 4b name + 4b flags + 4b null == 16 bytes)
// (...oh, and then the data, after the 15b of headers)
// ALL OUR USEFUL INFORMATION IS HELD IN THESE!!!
// TODO: handle the case of multiple data nodes? for instance multiple artworks
case 'data':
fseek($fd, $start + 16, SEEK_SET);
$parent_box[$name] = fread($fd, $length-16);
break;
// Container formats with flags (4 bytes)
case 'meta':
$i = $start + 12; // Keep going til we get to the end of this box
while($i < $end) $i = mp4_read_box($fd, $parent_box[$name], $i);
break;
// Discard this, it's all whitespace
case 'free':
break;
// Tags we either don't recognise or don't have children
default:
// Does this box have any actual data?
if($length > 8)
{
// OK, then find it...
fseek($fd, $start + 8, SEEK_SET);
// ...and put it in the target array structure
// $parent_box[$name] = fread($fd, $length-8);
// (... or don't because we don't need it and it wastes memory)
}
}
// Return the startpoint of the next box
return $end;
}
/**
* Entry point for MPEG4 metadata parsing.
* This function just calls mp4_read_box() on each of the top-level boxes,
* which recursively calls itself for each of their child boxes, thus
* traversing the tree.
*
* The file is validated with mp4_is_valid() before opening.
*
* @param $filename File to open
* @returns A recursive associative array of all the MPEG4 boxes this function knows about
*/
function mp4_unpack($filename)
{
// Check we have a real MPEG4
if(!mp4_is_valid($filename)) return false;
// Output data
$boxes = array();
// Open a file descriptor
if(!($fd = fopen($filename, 'rb'))) return false;
// Seek head for this loop
$offset = 0;
// Loop through the top-level boxes
while($offset < filesize($filename))
$offset = mp4_read_box($fd, $boxes, $offset);
// Close the file descriptor
fclose($fd);
return $boxes;
}
/**
* Incredibly quick function for checking if a file is an mpeg4 container
*
* This function opens up the file, checks for the first box, which in a valid
* mpeg4 container is always 'ftyp'. If it sees one, it returns true, if it doesn't,
* it assumes the file isn't mpeg4 and returns false.
* This is by no means robust, but it's cheap and fairly reliable.
*
* @returns true if the file is an mp4, false if not
*/
function mp4_is_valid($filename)
{
// If we can't open it, it's not an mp4
if(!$fd = fopen($filename, 'rb'))
return false;
// Seek to the place where the filetype box name should be
fseek($fd, 4, SEEK_SET);
// If it's there, it's an mp4, if not, it's not...
if(fread($fd, 4) == 'ftyp')
{
fclose($fd);
return true;
} else {
fclose($fd);
return false;
}
}
/**
* Filter for wp_update_attachment_metadata to hook in our mpeg4 metadata
*
* If the attachment is an mpeg4 container, extract iTunes tags and
* add them to the metadata, before it gets written to the database
*
* @param array $data Metadata already retrieved by Wordpress
* @param int $post_id ID of the attachment we're dealing with
*
* @returns Original metadata
*/
function mp4_metadata_filter($data, $post_id)
{
// Get $post variable, or bomb if we can't
$post_id = (int) $post_id;
if(!$post = &get_post($post_id)) return $data;
// Get the filename of our mp4
$filename = get_attached_file($post->ID);
// Open file as MPEG4, it'll return false if the file's not an MPEG4
if(false === $tags = mp4_unpack($filename)) return $data;
// Array of data to update in post
$postdata = array();
$postdata['ID'] = $post->ID;
// Handle moov.udta.meta.ilst.covr (iTunes Artwork)
// This function does not respect any wordpress settings with regard to
// thumbnail size, it just spits out whatever's in the mpeg4 file.
if(!empty($tags['moov']['udta']['meta']['ilst']['covr']['data']))
{
// Work out the path of our target thumbnail file
$artwork_filename = $filename.'-'.$post->ID.'-artwork.'.
$tags['moov']['udta']['meta']['ilst']['covr']['filetype'];
// Create & open the target file
if($fd = fopen($artwork_filename, 'xb'))
{
$len = strlen($tags['moov']['udta']['meta']['ilst']['covr']['data']);
// If our write is successful, add the artwork to the attachment metadata
if($len == fwrite($fd, $tags['moov']['udta']['meta']['ilst']['covr']['data'], $len))
{
$data['thumb'] = basename($artwork_filename);
}
fclose($fd);
}
}
// Handle moov.udta.meta.ilst.©art (iTunes Artist)
if(!empty($tags['moov']['udta']['meta']['ilst']['©art']['data']))
update_post_meta($post_id, '_artist',
$tags['moov']['udta']['meta']['ilst']['©art']['data']);
// Handle moov.udta.meta.ilst.©nam (iTunes Title)
if(!empty($tags['moov']['udta']['meta']['ilst']['©nam']['data']))
$postdata['post_title'] = $tags['moov']['udta']['meta']['ilst']['©nam']['data'];
// Handle moov.udta.meta.ilst.desc (iTunes Description)
if(!empty($tags['moov']['udta']['meta']['ilst']['desc']['data']))
$postdata['post_content'] = $tags['moov']['udta']['meta']['ilst']['desc']['data'];
// Handle moov.udta.meta.ilst.©day (iTunes Release Date)
if(!empty($tags['moov']['udta']['meta']['ilst']['©day']['data']))
{
$timestamp = strtotime($tags['moov']['udta']['meta']['ilst']['©day']['data']);
$postdata['post_date'] = date('Y-m-d H:i:s', $timestamp);
}
// Handle moov.udta.meta.ilst.purl (iTunes Podcast URL)
if(!empty($tags['moov']['udta']['meta']['ilst']['purl']['data']))
update_post_meta($post_id, '_podcast_url',
$tags['moov']['udta']['meta']['ilst']['purl']['data']);
// Push values from $postdata into the database
wp_update_post($postdata);
return $data;
}
add_filter('wp_update_attachment_metadata', 'mp4_metadata_filter', 12, 2);
?>