Example #1
0
        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()));
        }
Example #3
0
        static void UpdateCreationTimes(DateTime creationTime, Mp4 mp4)
        {
            Contract.Requires(mp4 != null);

            mp4.UpdateMvhd(creationTime, creationTime);
            mp4.UpdateTkhd(creationTime, creationTime);
            mp4.UpdateMdhd(creationTime, creationTime);
        }
Example #4
0
        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);
            }
        }
Example #5
0
        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;
                }
            }
        }
Example #6
0
        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());
            }
        }