我曾经成功地使用过exiftool
。以下是列出媒体文件中所有标签以及更新选定标签的命令(也可以批量处理文件):
C:\>exiftool.exe -short -groupNames test.mp4
[ExifTool] ExifToolVersion : 10.61
[File] FileName : test.mp4
[File] Directory : .
[File] FileSize : 91 MB
[File] FileModifyDate : 2018:06:30 19:25:34+05:00
[File] FileAccessDate : 2018:07:15 14:12:50+05:00
[File] FileCreateDate : 2018:07:15 14:12:50+05:00
[File] FilePermissions : rw-rw-rw-
[File] FileType : MP4
[File] FileTypeExtension : mp4
[File] MIMEType : video/mp4
[QuickTime] MajorBrand : MP4 v2 [ISO 14496-14]
[QuickTime] MinorVersion : 0.0.0
[QuickTime] CompatibleBrands : isom, mp42
[QuickTime] MovieDataSize : 95484206
[QuickTime] MovieDataOffset : 32
[QuickTime] MovieHeaderVersion : 0
[QuickTime] CreateDate : 2018:06:30 14:25:34
[QuickTime] ModifyDate : 2018:06:30 14:25:34
[QuickTime] TimeScale : 1000
[QuickTime] Duration : 0:01:02
-- snip --
[QuickTime] TrackCreateDate : 2018:06:30 14:25:34
[QuickTime] TrackModifyDate : 2018:06:30 14:25:34
-- snip --
[QuickTime] MediaCreateDate : 2018:06:30 14:25:34
[QuickTime] MediaModifyDate : 2018:06:30 14:25:34
-- snip --
C:\>exiftool.exe ^
-QuickTime:CreateDate="2018:07:15 13:15:00" ^
-QuickTime:ModifyDate="2018:07:15 13:15:00" ^
-QuickTime:TrackCreateDate="2018:07:15 13:15:00" ^
-QuickTime:TrackModifyDate="2018:07:15 13:15:00" ^
-QuickTime:MediaCreateDate="2018:07:15 13:15:00" ^
-QuickTime:MediaModifyDate="2018:07:15 13:15:00" ^
test.mp4
C:\>exiftool.exe -short -groupNames test.mp4
-- snip --
[File] FileModifyDate : 2018:07:15 14:19:52+05:00
[File] FileAccessDate : 2018:07:15 14:19:51+05:00
[File] FileCreateDate : 2018:07:15 14:19:39+05:00
-- snip --
[QuickTime] CreateDate : 2018:07:15 13:15:00
[QuickTime] ModifyDate : 2018:07:15 13:15:00
-- snip --
[QuickTime] TrackCreateDate : 2018:07:15 13:15:00
[QuickTime] TrackModifyDate : 2018:07:15 13:15:00
-- snip --
[QuickTime] MediaCreateDate : 2018:07:15 13:15:00
[QuickTime] MediaModifyDate : 2018:07:15 13:15:00
-- snip --
我通过直接编写读取/写入MP4文件格式来解决了这个问题。下面是VB代码:
Sub Main()
' Retrieve creation-time and modification-time, embedded inside the metadata of MP4 files
Dim ft = Mp4Times("a.mp4")
Console.WriteLine(ft.CreationTime)
Console.WriteLine(ft.ModificationTime)
' Update those times
Mp4Times("a.mp4", Date.Now, Date.Now)
End Sub
Class FileTimes
Public CreationTime As Date
Public ModificationTime As Date
End Class
Function Mp4Times(fn As String, Optional newCreationTime As Date? = Nothing, Optional newModificationTime As Date? = Nothing) As FileTimes
Dim ft As FileTimes
Using f = If(newCreationTime.HasValue OrElse newModificationTime.HasValue, IO.File.Open(fn, IO.FileMode.Open), IO.File.OpenRead(fn))
f.Seek(0, IO.SeekOrigin.End) : Dim fend = f.Position
' The file is made up of a sequence of boxes, with a standard way to find size and FourCC "kind" of each.
' Some box kinds contain a kind-specific blob of binary data. Other box kinds contain a sequence
' of sub-boxes. You need to look up the specs for each kind to know whether it has a blob or sub-boxes.
' We look for a top-level box of kind "moov", which contains sub-boxes, and then we look for its sub-box
' of kind "mvhd", which contains a binary blob. This is where Creation/ModificationTime are stored.
Dim pos = 0L, payloadStart = 0L, payloadEnd = 0L, boxKind = ""
While ReadNextBoxInfo(f, pos, fend, boxKind, payloadStart, payloadEnd) AndAlso boxKind <> "moov"
pos = payloadEnd
End While
If boxKind <> "moov" Then Return Nothing
pos = payloadStart : fend = payloadEnd
While ReadNextBoxInfo(f, pos, fend, boxKind, payloadStart, payloadEnd) AndAlso boxKind <> "mvhd"
pos = payloadEnd
End While
If boxKind <> "mvhd" Then Return Nothing
' The "mvhd" binary blob consists of 1byte (version, either 0 or 1), 3bytes (flags),
' and then either 4bytes (creation), 4bytes (modification)
' or 8bytes (creation), 8bytes (modification)
' If version=0 then it's the former, otherwise it's the later.
' In both cases "creation" and "modification" are big-endian number of seconds since 1st Jan 1904 UTC
f.Seek(pos + 8, IO.SeekOrigin.Begin) : Dim version = f.ReadByte()
f.Seek(pos + 12, IO.SeekOrigin.Begin)
Dim creationTime As Date, modificationTime As Date
'
If newCreationTime.HasValue Then
creationTime = newCreationTime.Value
If version = 0 Then Write4byteDate(f, creationTime) Else Write8byteDate(f, creationTime)
Else
creationTime = If(version = 0, ReadNext4byteDate(f), ReadNext8byteDate(f))
End If
'
If newModificationTime.HasValue Then
modificationTime = newModificationTime.Value
If version = 0 Then Write4byteDate(f, modificationTime) Else Write8byteDate(f, modificationTime)
Else
modificationTime = If(version = 0, ReadNext4byteDate(f), ReadNext8byteDate(f))
End If
ft = New FileTimes With {.CreationTime = creationTime, .ModificationTime = modificationTime}
End Using
If newCreationTime.HasValue Then IO.File.SetCreationTime(fn, newCreationTime.Value)
If newModificationTime.HasValue Then IO.File.SetLastWriteTime(fn, newModificationTime.Value)
Return ft
End Function
Function ReadNextBoxInfo(f As IO.Stream, pos As Long, fend As Long, ByRef boxKind As String, ByRef payloadStart As Long, ByRef payloadEnd As Long) As Boolean
boxKind = "" : payloadStart = 0 : payloadEnd = 0
If pos + 8 > fend Then Return False
Dim b(3) As Byte
f.Seek(pos, IO.SeekOrigin.Begin)
f.Read(b, 0, 4) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
Dim size = BitConverter.ToUInt32(b, 0)
f.Read(b, 0, 4)
Dim kind = ChrW(b(0)) & ChrW(b(1)) & ChrW(b(2)) & ChrW(b(3))
If size <> 1 Then
If pos + size > fend Then Return False
boxKind = kind : payloadStart = pos + 8 : payloadEnd = payloadStart + size - 8 : Return True
End If
If size = 1 AndAlso pos + 16 <= fend Then
ReDim b(7)
f.Read(b, 0, 8) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
Dim size2 = CLng(BitConverter.ToUInt64(b, 0))
If pos + size2 > fend Then Return False
boxKind = kind : payloadStart = pos + 16 : payloadEnd = payloadStart + size2 - 16 : Return True
End If
Return False
End Function
ReadOnly TZERO As Date = New Date(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc)
Function ReadNext4byteDate(f As IO.Stream) As Date
Dim b(3) As Byte
f.Read(b, 0, 4) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
Dim secs = BitConverter.ToUInt32(b, 0)
Return TZERO.AddSeconds(secs)
End Function
Function ReadNext8byteDate(f As IO.Stream) As Date
Dim b(7) As Byte
f.Read(b, 0, 8) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
Dim secs = BitConverter.ToUInt64(b, 0)
Return TZERO.AddSeconds(secs)
End Function
Sub Write4byteDate(f As IO.Stream, d As Date)
Dim secs = CUInt((d - TZERO).TotalSeconds)
Dim b = BitConverter.GetBytes(secs) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
f.Write(b, 0, 4)
End Sub
Sub Write8byteDate(f As IO.Stream, d As Date)
Dim secs = CULng((d - TZERO).TotalSeconds)
Dim b = BitConverter.GetBytes(secs) : If BitConverter.IsLittleEndian Then Array.Reverse(b)
f.Write(b, 0, 8)
End Sub
使用Windows Property System。参见Property Edit Sample以开始操作。请设置以下属性:
这是我编写的一个PowerShell脚本,使用exiftool分批处理。
它将把所有的exiftool命令写入CMD文件中。exiftool.exe需要与文件和exiftool.exe位于同一位置。
请确保Powershell ISE / PS1文件与您正在处理的文件在同一个目录中。 完成后,您可以在记事本中查看CMD文件。如果一切正常,请使用CMD运行它。 :)
$allFiles = Get-ChildItem -Filter * -Exclude *.exe
$exifFile = @()
$exifFile += "@echo off"
foreach($file in $allFiles)
{
$month = $file.CreationTime.Month
$day = $file.CreationTime.Day
$hour = $file.CreationTime.Hour
$min = $file.CreationTime.Minute
$sec = $file.CreationTime.Second
if($month -lt 10)
{
$month = "0$($file.CreationTime.Month)"
}
if($day -lt 10)
{
$day = "0$($file.CreationTime.Day)"
}
if($hour -lt 10)
{
$hour = "0$($file.CreationTime.Hour)"
}
if($min -lt 10)
{
$min = "0$($file.CreationTime.Minute)"
}
if($sec -lt 10)
{
$sec = "0$($file.CreationTime.Second)"
}
$exifFile += "exiftool.exe ^ -QuickTime:CreateDate=`"$($file.CreationTime.Year):$($month):$($day) $($hour):$($min):$($sec)`" ^ `"$($file.Name)`""
}
$exifFile | Out-File "MassCreateFix.cmd" -Encoding ascii -NoClobber