info: added metadata for Spherical Video V1
This commit is contained in:
parent
c93393a365
commit
e127a5ebca
3 changed files with 117 additions and 8 deletions
|
@ -21,17 +21,15 @@ import com.drew.metadata.iptc.IptcDirectory
|
||||||
import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory
|
import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory
|
||||||
import com.drew.metadata.webp.WebpDirectory
|
import com.drew.metadata.webp.WebpDirectory
|
||||||
import com.drew.metadata.xmp.XmpDirectory
|
import com.drew.metadata.xmp.XmpDirectory
|
||||||
|
import deckers.thibault.aves.metadata.*
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeInt
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeInt
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeRational
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeRational
|
||||||
import deckers.thibault.aves.metadata.Geotiff
|
|
||||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper
|
|
||||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDateMillis
|
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDateMillis
|
||||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDescription
|
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDescription
|
||||||
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt
|
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt
|
||||||
import deckers.thibault.aves.metadata.Metadata
|
|
||||||
import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode
|
import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode
|
||||||
import deckers.thibault.aves.metadata.Metadata.isFlippedForExifCode
|
import deckers.thibault.aves.metadata.Metadata.isFlippedForExifCode
|
||||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeBoolean
|
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeBoolean
|
||||||
|
@ -40,7 +38,6 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt
|
||||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational
|
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational
|
||||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString
|
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString
|
||||||
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isGeoTiff
|
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isGeoTiff
|
||||||
import deckers.thibault.aves.metadata.XMP
|
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeDateMillis
|
import deckers.thibault.aves.metadata.XMP.getSafeDateMillis
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeLocalizedText
|
import deckers.thibault.aves.metadata.XMP.getSafeLocalizedText
|
||||||
import deckers.thibault.aves.metadata.XMP.isPanorama
|
import deckers.thibault.aves.metadata.XMP.isPanorama
|
||||||
|
@ -142,6 +139,13 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
// remove this stat as it is not actual XMP data
|
// remove this stat as it is not actual XMP data
|
||||||
dirMap.remove(dir.getTagName(XmpDirectory.TAG_XMP_VALUE_COUNT))
|
dirMap.remove(dir.getTagName(XmpDirectory.TAG_XMP_VALUE_COUNT))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dir is Mp4UuidBoxDirectory) {
|
||||||
|
if (dir.getString(Mp4UuidBoxDirectory.TAG_UUID) == GSpherical.SPHERICAL_VIDEO_V1_UUID) {
|
||||||
|
val bytes = dir.getByteArray(Mp4UuidBoxDirectory.TAG_USER_DATA)
|
||||||
|
metadataMap["Spherical Video"] = HashMap(GSpherical(bytes).describe())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -348,7 +352,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
||||||
|
|
||||||
// identification of spherical video (aka 360° video)
|
// identification of spherical video (aka 360° video)
|
||||||
if (metadata.getDirectoriesOfType(Mp4UuidBoxDirectory::class.java).any {
|
if (metadata.getDirectoriesOfType(Mp4UuidBoxDirectory::class.java).any {
|
||||||
it.getString(Mp4UuidBoxDirectory.TAG_UUID) == Metadata.SPHERICAL_VIDEO_V1_UUID
|
it.getString(Mp4UuidBoxDirectory.TAG_UUID) == GSpherical.SPHERICAL_VIDEO_V1_UUID
|
||||||
}) {
|
}) {
|
||||||
flags = flags or MASK_IS_360
|
flags = flags or MASK_IS_360
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,6 @@ object Metadata {
|
||||||
// "+51.3328-000.7053+113.474/" (Apple)
|
// "+51.3328-000.7053+113.474/" (Apple)
|
||||||
val VIDEO_LOCATION_PATTERN: Pattern = Pattern.compile("([+-][.0-9]+)([+-][.0-9]+).*")
|
val VIDEO_LOCATION_PATTERN: Pattern = Pattern.compile("([+-][.0-9]+)([+-][.0-9]+).*")
|
||||||
|
|
||||||
// cf https://github.com/google/spatial-media
|
|
||||||
const val SPHERICAL_VIDEO_V1_UUID = "ffcc8263-f855-4a93-8814-587a02521fdd"
|
|
||||||
|
|
||||||
// directory names, as shown when listing all metadata
|
// directory names, as shown when listing all metadata
|
||||||
const val DIR_GPS = "GPS" // from metadata-extractor
|
const val DIR_GPS = "GPS" // from metadata-extractor
|
||||||
const val DIR_XMP = "XMP" // from metadata-extractor
|
const val DIR_XMP = "XMP" // from metadata-extractor
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package deckers.thibault.aves.metadata
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Xml
|
||||||
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
|
class GSpherical(bytes: ByteArray) {
|
||||||
|
var spherical: Boolean = false
|
||||||
|
var stitched: Boolean = false
|
||||||
|
var stitchingSoftware: String = ""
|
||||||
|
var projectionType: String = ""
|
||||||
|
var stereoMode: String? = null
|
||||||
|
var sourceCount: Int? = null
|
||||||
|
var initialViewHeadingDegrees: Int? = null
|
||||||
|
var initialViewPitchDegrees: Int? = null
|
||||||
|
var initialViewRollDegrees: Int? = null
|
||||||
|
var timestamp: Int? = null
|
||||||
|
var fullPanoWidthPixels: Int? = null
|
||||||
|
var fullPanoHeightPixels: Int? = null
|
||||||
|
var croppedAreaImageWidthPixels: Int? = null
|
||||||
|
var croppedAreaImageHeightPixels: Int? = null
|
||||||
|
var croppedAreaLeftPixels: Int? = null
|
||||||
|
var croppedAreaTopPixels: Int? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
ByteArrayInputStream(bytes).use {
|
||||||
|
val parser = Xml.newPullParser().apply {
|
||||||
|
setInput(it, null)
|
||||||
|
nextTag()
|
||||||
|
require(XmlPullParser.START_TAG, RDF_NS, "SphericalVideo")
|
||||||
|
}
|
||||||
|
while (parser.next() != XmlPullParser.END_TAG) {
|
||||||
|
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||||
|
if (parser.namespace == GSPHERICAL_NS) {
|
||||||
|
when (val tag = parser.name) {
|
||||||
|
"Spherical" -> spherical = readTag(parser, tag) == "true"
|
||||||
|
"Stitched" -> stitched = readTag(parser, tag) == "true"
|
||||||
|
"StitchingSoftware" -> stitchingSoftware = readTag(parser, tag)
|
||||||
|
"ProjectionType" -> projectionType = readTag(parser, tag)
|
||||||
|
"StereoMode" -> stereoMode = readTag(parser, tag)
|
||||||
|
"SourceCount" -> sourceCount = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"InitialViewHeadingDegrees" -> initialViewHeadingDegrees = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"InitialViewPitchDegrees" -> initialViewPitchDegrees = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"InitialViewRollDegrees" -> initialViewRollDegrees = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"Timestamp" -> timestamp = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"FullPanoWidthPixels" -> fullPanoWidthPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"FullPanoHeightPixels" -> fullPanoHeightPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"CroppedAreaImageWidthPixels" -> croppedAreaImageWidthPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"CroppedAreaImageHeightPixels" -> croppedAreaImageHeightPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"CroppedAreaLeftPixels" -> croppedAreaLeftPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
"CroppedAreaTopPixels" -> croppedAreaTopPixels = Integer.parseInt(readTag(parser, tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to parse XML", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun describe(): Map<String, String?> = hashMapOf(
|
||||||
|
"Spherical" to spherical.toString(),
|
||||||
|
"Stitched" to stitched.toString(),
|
||||||
|
"Stitching Software" to stitchingSoftware,
|
||||||
|
"Projection Type" to projectionType,
|
||||||
|
"Stereo Mode" to stereoMode,
|
||||||
|
"Source Count" to sourceCount?.toString(),
|
||||||
|
"Initial View Heading Degrees" to initialViewHeadingDegrees?.toString(),
|
||||||
|
"Initial View Pitch Degrees" to initialViewPitchDegrees?.toString(),
|
||||||
|
"Initial View Roll Degrees" to initialViewRollDegrees?.toString(),
|
||||||
|
"Timestamp" to timestamp?.toString(),
|
||||||
|
"Full Panorama Width Pixels" to fullPanoWidthPixels?.toString(),
|
||||||
|
"Full Panorama Height Pixels" to fullPanoHeightPixels?.toString(),
|
||||||
|
"Cropped Area Image Width Pixels" to croppedAreaImageWidthPixels?.toString(),
|
||||||
|
"Cropped Area Image Height Pixels" to croppedAreaImageHeightPixels?.toString(),
|
||||||
|
"Cropped Area Left Pixels" to croppedAreaLeftPixels?.toString(),
|
||||||
|
"Cropped Area Top Pixels" to croppedAreaTopPixels?.toString(),
|
||||||
|
).filterValues { it != null }
|
||||||
|
|
||||||
|
companion object SphericalVideo {
|
||||||
|
private val LOG_TAG = LogUtils.createTag(SphericalVideo::class.java)
|
||||||
|
|
||||||
|
// cf https://github.com/google/spatial-media
|
||||||
|
const val SPHERICAL_VIDEO_V1_UUID = "ffcc8263-f855-4a93-8814-587a02521fdd"
|
||||||
|
|
||||||
|
const val RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
const val GSPHERICAL_NS = "http://ns.google.com/videos/1.0/spherical/"
|
||||||
|
|
||||||
|
private fun readText(parser: XmlPullParser): String {
|
||||||
|
var text = ""
|
||||||
|
if (parser.next() == XmlPullParser.TEXT) {
|
||||||
|
text = parser.text
|
||||||
|
parser.nextTag()
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readTag(parser: XmlPullParser, tag: String): String {
|
||||||
|
parser.require(XmlPullParser.START_TAG, GSPHERICAL_NS, tag)
|
||||||
|
val text = readText(parser)
|
||||||
|
parser.require(XmlPullParser.END_TAG, GSPHERICAL_NS, tag)
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue