public AudioInfo ReadAudioInfo(Stream stream) { Contract.Ensures(Contract.Result <AudioInfo>() != null); var mp4 = new Mp4(stream); uint dataSize = mp4.GetChildAtomInfo().Single(atom => atom.FourCC == "mdat").Size; mp4.DescendToAtom("moov", "trak", "mdia", "minf", "stbl", "stts"); var stts = new SttsAtom(mp4.ReadAtom(mp4.CurrentAtom)); uint sampleCount = stts.PacketCount * stts.PacketSize; mp4.DescendToAtom("moov", "trak", "mdia", "minf", "stbl", "stsd", "mp4a", "esds"); var esds = new EsdsAtom(mp4.ReadAtom(mp4.CurrentAtom)); if (esds.SampleRate > 0) { return(new AudioInfo(string.Format(CultureInfo.CurrentCulture, "{0}kbps MPEG 4 AAC", esds.AverageBitrate > 0 ? (int)esds.AverageBitrate / 1000 : CalculateBitRate(dataSize, sampleCount, esds.SampleRate)), esds.Channels, 0, (int)esds.SampleRate, sampleCount)); } // Apple Lossless files have their own atom for storing audio info: mp4.DescendToAtom("moov", "trak", "mdia", "minf", "stbl", "stsd", "alac"); var alac = new AlacAtom(mp4.ReadAtom(mp4.CurrentAtom)); return(new AudioInfo("Apple Lossless", alac.Channels, alac.BitsPerSample, (int)alac.SampleRate, sampleCount)); }
public MetadataDictionary ReadMetadata(Stream stream) { var mp4 = new Mp4(stream); mp4.DescendToAtom("moov", "udta", "meta", "ilst"); return(new AtomToMetadataAdapter(mp4, mp4.GetChildAtomInfo())); }
static void UpdateCreationTimes(DateTime creationTime, Mp4 mp4) { Contract.Requires(mp4 != null); mp4.UpdateMvhd(creationTime, creationTime); mp4.UpdateTkhd(creationTime, creationTime); mp4.UpdateMdhd(creationTime, creationTime); }
public void WriteMetadata(Stream stream, MetadataDictionary metadata, SettingsDictionary settings) { var originalMp4 = new Mp4(stream); AtomInfo[] topAtoms = originalMp4.GetChildAtomInfo(); // Create a temporary stream to hold the new atom structure: using (var tempStream = new MemoryStream()) { var tempMp4 = new Mp4(tempStream); // Copy the ftyp and moov atoms to the temporary stream: originalMp4.CopyAtom(topAtoms.Single(atom => atom.FourCC == "ftyp"), tempStream); originalMp4.CopyAtom(topAtoms.Single(atom => atom.FourCC == "moov"), tempStream); // Move to the start of the list atom: originalMp4.DescendToAtom("moov", "udta", "meta", "ilst"); tempMp4.DescendToAtom("moov", "udta", "meta", "ilst"); // Remove any copied ilst atoms, then generate new ones: tempStream.SetLength(tempStream.Position); byte[] ilstData = GenerateIlst(originalMp4, metadata, settings); tempStream.Write(ilstData, 0, ilstData.Length); // Update the ilst and parent atom sizes: tempMp4.UpdateAtomSizes((uint)tempStream.Length - tempMp4.CurrentAtom.End); // Update the creation times if they're being set explicitly: DateTime creationTime; if (!string.IsNullOrEmpty(settings["CreationTime"])) { if (DateTime.TryParse(settings["CreationTime"], out creationTime)) { UpdateCreationTimes(creationTime, tempMp4); } else { throw new InvalidSettingException(string.Format(CultureInfo.CurrentCulture, Resources.MetadataEncoderBadCreationTime, settings["CreationTime"])); } } // Update the stco atom to reflect the new location of mdat: var mdatOffset = (int)(tempStream.Length - topAtoms.Single(atom => atom.FourCC == "mdat").Start); tempMp4.UpdateStco(mdatOffset); // Copy the mdat atom to the temporary stream, after the moov atom: tempStream.Seek(0, SeekOrigin.End); originalMp4.CopyAtom(topAtoms.Single(atom => atom.FourCC == "mdat"), tempStream); // Overwrite the original stream with the new, optimized one: stream.SetLength(tempStream.Length); stream.Position = 0; tempStream.Position = 0; tempStream.CopyTo(stream); } }
internal AtomToMetadataAdapter(Mp4 mp4, AtomInfo[] atoms) { Contract.Requires(mp4 != null); Contract.Requires(atoms != null); foreach (AtomInfo atom in atoms) { byte[] atomData = mp4.ReadAtom(atom); switch (atom.FourCC) { case "trkn": var trackNumberAtom = new TrackNumberAtom(atomData); Add("TrackNumber", trackNumberAtom.TrackNumber.ToString(CultureInfo.InvariantCulture)); if (trackNumberAtom.TrackCount > 0) { Add("TrackCount", trackNumberAtom.TrackCount.ToString(CultureInfo.InvariantCulture)); } break; case "©day": // The ©day atom may contain a full date, or only the year: var dayAtom = new TextAtom(atomData); DateTime result; if (DateTime.TryParse(dayAtom.Value, CultureInfo.CurrentCulture, DateTimeStyles.NoCurrentDateDefault, out result) && result.Year >= 1000) { base["Day"] = result.Day.ToString(CultureInfo.InvariantCulture); base["Month"] = result.Month.ToString(CultureInfo.InvariantCulture); base["Year"] = result.Year.ToString(CultureInfo.InvariantCulture); } else { base["Year"] = dayAtom.Value; } break; case "covr": try { CoverArt = new CoverArt(new CovrAtom(atomData).Value); } catch (UnsupportedCoverArtException) { } break; default: string mappedKey; if (_map.TryGetValue(atom.FourCC, out mappedKey)) { base[mappedKey] = new TextAtom(atomData).Value; } break; } } }
static byte[] GenerateIlst(Mp4 originalMp4, MetadataDictionary metadata, SettingsDictionary settings) { Contract.Requires(originalMp4 != null); Contract.Requires(metadata != null); Contract.Requires(settings != null); Contract.Ensures(Contract.Result <byte[]>() != null); using (var resultStream = new MemoryStream()) { var adaptedMetadata = new MetadataToAtomAdapter(metadata, settings); // "Reverse DNS" atoms may need to be preserved: foreach (ReverseDnsAtom reverseDnsAtom in from listAtom in originalMp4.GetChildAtomInfo() where listAtom.FourCC == "----" select new ReverseDnsAtom(originalMp4.ReadAtom(listAtom))) { switch (reverseDnsAtom.Name) { // Always preserve the iTunSMPB (gapless playback) atom: case "iTunSMPB": resultStream.Write(reverseDnsAtom.GetBytes(), 0, reverseDnsAtom.GetBytes().Length); break; // Preserve the existing iTunNORM atom if a new one isn't provided, and AddSoundCheck isn't explicitly False: case "iTunNORM": if (!adaptedMetadata.IncludesSoundCheck && string.Compare(settings["AddSoundCheck"], bool.FalseString, StringComparison.OrdinalIgnoreCase) != 0) { resultStream.Write(reverseDnsAtom.GetBytes(), 0, reverseDnsAtom.GetBytes().Length); } break; } } byte[] atomData = adaptedMetadata.GetBytes(); resultStream.Write(atomData, 0, atomData.Length); return(resultStream.ToArray()); } }