-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example of handler creation and box implementation for MP4 Tika work #3
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,6 @@ public boolean shouldAcceptBox(@NotNull Box box) | |
|| box.type.equals(Mp4BoxTypes.BOX_HANDLER) | ||
|| box.type.equals(Mp4BoxTypes.BOX_MEDIA_HEADER) | ||
|| box.type.equals(Mp4BoxTypes.BOX_TRACK_HEADER) | ||
|| box.type.equals(Mp4BoxTypes.BOX_USER_DATA) | ||
|| box.type.equals(Mp4BoxTypes.BOX_USER_DEFINED); | ||
} | ||
|
||
|
@@ -68,7 +67,8 @@ public boolean shouldAcceptContainer(@NotNull Box box) | |
return box.type.equals(Mp4ContainerTypes.BOX_TRACK) | ||
|| box.type.equals(Mp4ContainerTypes.BOX_METADATA) | ||
|| box.type.equals(Mp4ContainerTypes.BOX_MOVIE) | ||
|| box.type.equals(Mp4ContainerTypes.BOX_MEDIA); | ||
|| box.type.equals(Mp4ContainerTypes.BOX_MEDIA) | ||
|| box.type.equals(Mp4ContainerTypes.BOX_USER_DATA); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Officially add "udta" as a container type. |
||
} | ||
|
||
@Override | ||
|
@@ -90,8 +90,6 @@ public Mp4Handler<?> processBox(@NotNull Box box, @Nullable byte[] payload, Mp4C | |
} else if (box.type.equals(Mp4BoxTypes.BOX_USER_DEFINED)) { | ||
Mp4UuidBoxHandler userBoxHandler = new Mp4UuidBoxHandler(metadata); | ||
userBoxHandler.processBox(box, payload, context); | ||
} else if (box.type.equals(Mp4BoxTypes.BOX_USER_DATA)) { | ||
processUserData(box, reader, payload.length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "udta" is a container. We can move this underlying logic to the ItemListBox (where it belongs). |
||
} | ||
} else { | ||
if (box.type.equals(Mp4ContainerTypes.BOX_COMPRESSED_MOVIE)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,9 @@ | |
package com.drew.metadata.mp4; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
/** | ||
* @author Payton Garland | ||
|
@@ -39,7 +42,7 @@ public class Mp4BoxTypes | |
public static final String BOX_MEDIA_HEADER = "mdhd"; | ||
public static final String BOX_TRACK_HEADER = "tkhd"; | ||
public static final String BOX_USER_DEFINED = "uuid"; | ||
public static final String BOX_USER_DATA = "udta"; | ||
public static final String BOX_ITEM_LIST = "ilst"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tika MP4 changes essentially implements the "ilst" box. |
||
|
||
private static final ArrayList<String> _boxList = new ArrayList<String>(); | ||
|
||
|
@@ -56,5 +59,6 @@ public class Mp4BoxTypes | |
_boxList.add(BOX_MEDIA_HEADER); | ||
_boxList.add(BOX_TRACK_HEADER); | ||
_boxList.add(BOX_USER_DEFINED); | ||
_boxList.add(BOX_ITEM_LIST); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,8 @@ public class Mp4HandlerFactory | |
private static final String HANDLER_VIDEO_MEDIA = "vide"; | ||
private static final String HANDLER_HINT_MEDIA = "hint"; | ||
private static final String HANDLER_TEXT_MEDIA = "text"; | ||
private static final String HANDLER_META_MEDIA = "meta"; | ||
private static final String HANDLER_NRT_META_MEDIA = "meta"; | ||
private static final String HANDLER_META_MEDIA = "mdir"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "ilst" box lies under the "mdir" (metadata) handler. Documented under "QuickTime Handler Keys" https://exiftool.org/TagNames/QuickTime.html |
||
|
||
private Mp4Handler<?> caller; | ||
|
||
|
@@ -51,6 +52,8 @@ public Mp4Handler<?> getHandler(HandlerBox box, Metadata metadata, Mp4Context co | |
return new Mp4HintHandler(metadata, context); | ||
} else if (type.equals(HANDLER_TEXT_MEDIA)) { | ||
return new Mp4TextHandler(metadata, context); | ||
} else if (type.equals(HANDLER_NRT_META_MEDIA)) { | ||
return new Mp4NRTMetaHandler(metadata, context); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is the best naming of the old "meta" handler. This is what ExifTool calls it [NRT Metadata], but I think we should verify if this naming is proper. |
||
} else if (type.equals(HANDLER_META_MEDIA)) { | ||
return new Mp4MetaHandler(metadata, context); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* Copyright 2002-2019 Drew Noakes and contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* More information about this project is available at: | ||
* | ||
* https://drewnoakes.com/code/exif/ | ||
* https://github.com/drewnoakes/metadata-extractor | ||
*/ | ||
package com.drew.metadata.mp4.boxes; | ||
|
||
import com.drew.lang.SequentialReader; | ||
import com.drew.metadata.mp4.media.Mp4MetaBoxDirectory; | ||
|
||
import java.io.EOFException; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
|
||
public class ItemListBox extends Box | ||
{ | ||
private class ItemListEntry | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hacky key/value class for our itemMap such that we can map the item list key to the directory key (and actual value). |
||
{ | ||
private int tag; | ||
private String val; | ||
|
||
ItemListEntry(int tag) { | ||
this.tag = tag; | ||
this.val = null; | ||
} | ||
|
||
int getTag() { | ||
return this.tag; | ||
} | ||
|
||
void setVal(String val) { | ||
this.val = val; | ||
} | ||
|
||
String getVal() { | ||
return this.val; | ||
} | ||
} | ||
|
||
public static Map<String, ItemListEntry> itemMap; | ||
|
||
{ | ||
itemMap = new HashMap<String, ItemListEntry>(); | ||
|
||
itemMap.put("�alb", new ItemListEntry(Mp4MetaBoxDirectory.TAG_ALBUM)); | ||
itemMap.put("aART", new ItemListEntry(Mp4MetaBoxDirectory.TAG_ALBUM_ARTIST)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maps the item list keys to their corresponding directory key (and value if set later) |
||
} | ||
|
||
public ItemListBox(SequentialReader reader, Box box) throws IOException | ||
{ | ||
super(box); | ||
|
||
long totalRead = 0; | ||
try { | ||
while (totalRead < box.size) { | ||
long recordLen = reader.getUInt32(); | ||
String fieldName = reader.getString(4); | ||
long fieldLen = reader.getUInt32(); | ||
String typeName = reader.getString(4);//data | ||
totalRead += 16; | ||
if ("data".equals(typeName)) { | ||
reader.skip(8);//not sure what these are | ||
totalRead += 8; | ||
int toRead = (int) fieldLen - 16; | ||
if (toRead <= 0) { | ||
//log? | ||
return; | ||
} | ||
if ("covr".equals(fieldName)) { | ||
//covr can be an image file, e.g. png or jpeg | ||
//skip this for now | ||
reader.skip(toRead); | ||
} else if ("cpil".equals(fieldName)) { | ||
int compilationId = (int) reader.getByte(); | ||
// metadata.set(XMPDM.COMPILATION, compilationId); | ||
} else if ("trkn".equals(fieldName)) { | ||
if (toRead == 8) { | ||
long numA = reader.getUInt32(); | ||
long numB = reader.getUInt32(); | ||
// metadata.set(XMPDM.TRACK_NUMBER, (int)numA); | ||
} else { | ||
//log | ||
reader.skip(toRead); | ||
} | ||
} else if ("disk".equals(fieldName)) { | ||
int a = reader.getInt32(); | ||
short b = reader.getInt16(); | ||
// metadata.set(XMPDM.DISC_NUMBER, a); | ||
} else { | ||
String val = reader.getString(toRead); | ||
if (itemMap.containsKey(fieldName)) { | ||
ItemListEntry entry = itemMap.get(fieldName); | ||
entry.setVal(val); | ||
itemMap.put(fieldName, entry); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't really touch the "ilst" processing part except for right here. We add everything to the item map if it is known. |
||
} | ||
// try { | ||
// addMetadata(fieldName, val); | ||
// } catch (SAXException e) { | ||
// //need to punch through IOException catching in MP4Reader | ||
// throw new RuntimeSAXException(e); | ||
// } | ||
} | ||
|
||
totalRead += toRead; | ||
} else { | ||
int toSkip = (int) recordLen - 16; | ||
if (toSkip <= 0) { | ||
//log? | ||
return; | ||
} | ||
reader.skip(toSkip); | ||
totalRead += toSkip; | ||
} | ||
} | ||
} catch (EOFException e) { | ||
System.out.println("EOF reached"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation needs to be modified to avoid EOF. Apple documents how to properly parse this atom, but upon first glance, it's a bit involved https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html I don't mind leaving this catch here for now. |
||
} | ||
} | ||
|
||
public void addMetadata(Mp4MetaBoxDirectory directory) | ||
{ | ||
for (Map.Entry<String, ItemListEntry> entry : itemMap.entrySet()) { | ||
if (entry.getValue().getVal() != null) { | ||
directory.setString(entry.getValue().getTag(), entry.getValue().getVal()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, we iterate over the entire itemMap that we have been populating. If the value isn't null, then we add it to the directory. There are a couple of issues as is:
|
||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ public class UserDataBox extends Box { | |
public UserDataBox(@NotNull final SequentialReader reader, @NotNull final Box box, int length) throws IOException { | ||
super(box); | ||
|
||
// TODO: This logic should be added to ItemListBox by adding a new item for "©xyz" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the original location support that Drew added a while back. Since we are implementing the ItemListbox, it can actually be moved to the new ItemListBox file. |
||
while (reader.getPosition() < length) { | ||
long size = reader.getUInt32(); | ||
if (size <= 4) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright 2002-2019 Drew Noakes and contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* More information about this project is available at: | ||
* | ||
* https://drewnoakes.com/code/exif/ | ||
* https://github.com/drewnoakes/metadata-extractor | ||
*/ | ||
package com.drew.metadata.mp4.media; | ||
|
||
import com.drew.metadata.TagDescriptor; | ||
|
||
public class Mp4MetaBoxDescriptor extends TagDescriptor<Mp4MetaBoxDirectory> | ||
{ | ||
public Mp4MetaBoxDescriptor(Mp4MetaBoxDirectory directory) | ||
{ | ||
super(directory); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright 2002-2019 Drew Noakes and contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* More information about this project is available at: | ||
* | ||
* https://drewnoakes.com/code/exif/ | ||
* https://github.com/drewnoakes/metadata-extractor | ||
*/ | ||
package com.drew.metadata.mp4.media; | ||
|
||
import com.drew.lang.annotations.NotNull; | ||
|
||
import java.util.HashMap; | ||
|
||
public class Mp4MetaBoxDirectory extends Mp4MediaDirectory | ||
{ | ||
public static final Integer TAG_ALBUM = 1001; | ||
public static final Integer TAG_ALBUM_ARTIST = 1002; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add all of the ItemList keys that are known here. |
||
|
||
@NotNull | ||
private static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>(); | ||
|
||
static | ||
{ | ||
Mp4MetaBoxDirectory.addMp4MediaTags(_tagNameMap); | ||
_tagNameMap.put(TAG_ALBUM, "Album"); | ||
_tagNameMap.put(TAG_ALBUM_ARTIST, "Album Artist"); | ||
} | ||
|
||
public Mp4MetaBoxDirectory() | ||
{ | ||
this.setDescriptor(new Mp4MetaBoxDescriptor(this)); | ||
} | ||
|
||
@NotNull | ||
@Override | ||
public String getName() | ||
{ | ||
return "QuickTime Metadata"; | ||
} | ||
|
||
@NotNull | ||
@Override | ||
protected HashMap<Integer, String> getTagNameMap() | ||
{ | ||
return _tagNameMap; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure the best way to do this.
We consider "meta" boxes to be a container, which they are. However, they are also technically full box implementations, so they have an extra 4 bytes. This is a hacky way to bypass this information for now and continue reading into the container.