【问题标题】:How to set the "Media created" date on an MPEG-4 file如何在 MPEG-4 文件上设置“媒体创建”日期
【发布时间】:2012-04-16 10:02:43
【问题描述】:

我有许多从各种数码相机格式转码的 MPEG-4 文件,这些文件系统的修改日期是正确的。我想将“媒体创建”标签设置为匹配。这可以通过“属性”窗口的“详细信息”选项卡在 Windows 资源管理器中手动完成。设置 Media Created 很有用,因为 Windows Live Photo Gallery 为它的 Date Taken 属性关闭此字段。不幸的是,文件的数量使得手动设置所有日期变得不切实际。

自动化的几种途径具有潜力。 TagLib# 似乎支持所有 MP4 标签,但获取更多基本标签的 API 尚不清楚。另一个角度是 Windows 外壳。据推测,Windows Explorer 正在使用它来编写标签。通过 shell 有一个example for reading,但似乎没有用于编写的 API。

【问题讨论】:

    标签: tags taglib windows-shell mpeg-4 taglib-sharp


    【解决方案1】:

    这是我编写的一个 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
    

    【讨论】:

      【解决方案2】:

      我在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 --
      

      【讨论】:

      • exiftool 似乎没有将 Track 和 Media 日期识别为日期(它会忽略 -d!)
      【解决方案3】:

      我通过直接写读/写 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
      

      【讨论】:

        【解决方案4】:

        使用Windows Property System。请参阅Property Edit Sample 以开始使用。设置这些属性:

        • System.DateImported
        • System.Media.DateEncoded
        • System.ItemDate

        【讨论】:

          猜你喜欢
          • 2015-10-09
          • 1970-01-01
          • 2011-06-17
          • 1970-01-01
          • 2012-01-11
          • 1970-01-01
          • 2020-01-01
          • 2019-12-22
          • 1970-01-01
          相关资源
          最近更新 更多