public bool Write(BinaryReader r, BinaryWriter w, TagData tag) { long oldTagSize; long newTagSize; long cumulativeDelta = 0; bool result = true; // Constraint-check on non-supported values if (FieldCodeFixedLength > 0) { if (tag.Pictures != null) { foreach (PictureInfo picInfo in tag.Pictures) { if (PictureInfo.PIC_TYPE.Unsupported.Equals(picInfo.PicType) && (picInfo.TagType.Equals(getImplementedTagType()))) { if ((-1 == picInfo.NativePicCode) && (Utils.ProtectValue(picInfo.NativePicCodeStr).Length != FieldCodeFixedLength)) { throw new NotSupportedException("Field code fixed length is " + FieldCodeFixedLength + "; detected field '" + Utils.ProtectValue(picInfo.NativePicCodeStr) + "' is " + Utils.ProtectValue(picInfo.NativePicCodeStr).Length + " characters long and cannot be written"); } } } } foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { if (fieldInfo.TagType.Equals(getImplementedTagType()) || MetaDataIOFactory.TAG_ANY == fieldInfo.TagType) { if (Utils.ProtectValue(fieldInfo.NativeFieldCode).Length != FieldCodeFixedLength) { throw new NotSupportedException("Field code fixed length is " + FieldCodeFixedLength + "; detected field '" + Utils.ProtectValue(fieldInfo.NativeFieldCode) + "' is " + Utils.ProtectValue(fieldInfo.NativeFieldCode).Length + " characters long and cannot be written"); } } } } structureHelper.Clear(); tagData.Pictures.Clear(); // Read all the fields in the existing tag (including unsupported fields) ReadTagParams readTagParams = new ReadTagParams(true, true); readTagParams.PrepareForWriting = true; if (embedder != null && embedder.HasEmbeddedID3v2 > 0) { readTagParams.offset = embedder.HasEmbeddedID3v2; } this.read(r, readTagParams); if (embedder != null && getImplementedTagType() == MetaDataIOFactory.TAG_ID3V2) { structureHelper.Clear(); structureHelper.AddZone(embedder.Id3v2Zone); } // Give engine something to work with if the tag is really empty if (!tagExists && 0 == Zones.Count) { structureHelper.AddZone(0, 0); } TagData dataToWrite; dataToWrite = tagData; dataToWrite.IntegrateValues(tag); // Merge existing information + new tag information foreach (Zone zone in Zones) { oldTagSize = zone.Size; bool isTagWritten; // Write new tag to a MemoryStream using (MemoryStream s = new MemoryStream(zone.Size)) using (BinaryWriter msw = new BinaryWriter(s, Settings.DefaultTextEncoding)) { if (write(dataToWrite, msw, zone.Name) > 0) { isTagWritten = true; newTagSize = s.Length; if (embedder != null && getImplementedTagType() == MetaDataIOFactory.TAG_ID3V2 && embedder.ID3v2EmbeddingHeaderSize > 0) { StreamUtils.LengthenStream(s, 0, embedder.ID3v2EmbeddingHeaderSize); s.Position = 0; embedder.WriteID3v2EmbeddingHeader(msw, newTagSize); newTagSize = s.Length; } } else { isTagWritten = false; newTagSize = zone.CoreSignature.Length; } // -- Adjust tag slot to new size in file -- long tagBeginOffset, tagEndOffset; if (tagExists && zone.Size > zone.CoreSignature.Length) // An existing tag has been reprocessed { tagBeginOffset = zone.Offset + cumulativeDelta; tagEndOffset = tagBeginOffset + zone.Size; } else // A brand new tag has been added to the file { if (embedder != null && getImplementedTagType() == MetaDataIOFactory.TAG_ID3V2) { tagBeginOffset = embedder.Id3v2Zone.Offset; } else { switch (getDefaultTagOffset()) { case TO_EOF: tagBeginOffset = r.BaseStream.Length; break; case TO_BOF: tagBeginOffset = 0; break; case TO_BUILTIN: tagBeginOffset = zone.Offset + cumulativeDelta; break; default: tagBeginOffset = -1; break; } } tagEndOffset = tagBeginOffset + zone.Size; } // Need to build a larger file if (newTagSize > zone.Size) { StreamUtils.LengthenStream(w.BaseStream, tagEndOffset, (uint)(newTagSize - zone.Size)); } else if (newTagSize < zone.Size) // Need to reduce file size { StreamUtils.ShortenStream(w.BaseStream, tagEndOffset, (uint)(zone.Size - newTagSize)); } // Copy tag contents to the new slot r.BaseStream.Seek(tagBeginOffset, SeekOrigin.Begin); s.Seek(0, SeekOrigin.Begin); if (isTagWritten) { StreamUtils.CopyStream(s, w.BaseStream); } else { if (zone.CoreSignature.Length > 0) { msw.Write(zone.CoreSignature); } } int delta = (int)(newTagSize - oldTagSize); cumulativeDelta += delta; // Edit wrapping size markers and frame counters if needed if (delta != 0 && (MetaDataIOFactory.TAG_NATIVE == getImplementedTagType() || (embedder != null && getImplementedTagType() == MetaDataIOFactory.TAG_ID3V2))) { int action; if (oldTagSize == zone.CoreSignature.Length && isTagWritten) { action = ACTION_ADD; } else if (newTagSize == zone.CoreSignature.Length && !isTagWritten) { action = ACTION_DELETE; } else { action = ACTION_EDIT; } result = structureHelper.RewriteHeaders(w, delta, action, zone.Name); } } } // Loop through zones // Update tag information without calling Read /* TODO - this implementation is too risky : * - if one of the writing operations fails, data is updated as if everything went right * - any picture slot with a markForDeletion flag is recorded as-is in the tag */ tagData = dataToWrite; return(result); }
private bool RewriteZonesDirect( BinaryWriter w, WriteDelegate write, ICollection <Zone> zones, TagData dataToWrite, bool tagExists, long globalOffsetCorrection = 0, bool buffered = false) { long oldTagSize; long newTagSize; long cumulativeDelta = 0; bool result = true; // totalProgressSteps += zones.Count; foreach (Zone zone in zones) { oldTagSize = zone.Size; // Write new tag to a MemoryStream using (MemoryStream s = new MemoryStream(zone.Size)) using (BinaryWriter msw = new BinaryWriter(s, Settings.DefaultTextEncoding)) { dataToWrite.DataSizeDelta = cumulativeDelta; WriteResult writeResult = write(msw, dataToWrite, zone); if (WriteMode.REPLACE == writeResult.RequiredMode) { if (writeResult.WrittenFields > 0) { newTagSize = s.Length; if (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2 && embedder.ID3v2EmbeddingHeaderSize > 0) { StreamUtils.LengthenStream(s, 0, embedder.ID3v2EmbeddingHeaderSize); s.Position = 0; embedder.WriteID3v2EmbeddingHeader(msw, newTagSize); newTagSize = s.Length; } } else { newTagSize = zone.CoreSignature.Length; } } else // Overwrite mode { newTagSize = zone.Size; } // -- Adjust tag slot to new size in file -- long tagBeginOffset, tagEndOffset; if (tagExists && zone.Size > zone.CoreSignature.Length) // An existing tag has been reprocessed { tagBeginOffset = zone.Offset + cumulativeDelta - globalOffsetCorrection; tagEndOffset = tagBeginOffset + zone.Size; } else // A brand new tag has been added to the file { if (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2) { tagBeginOffset = embedder.Id3v2Zone.Offset - globalOffsetCorrection; } else { switch (defaultTagOffset) { case MetaDataIO.TO_EOF: tagBeginOffset = w.BaseStream.Length; break; case MetaDataIO.TO_BOF: tagBeginOffset = 0; break; case MetaDataIO.TO_BUILTIN: tagBeginOffset = zone.Offset + cumulativeDelta; break; default: tagBeginOffset = -1; break; } tagBeginOffset -= globalOffsetCorrection; } tagEndOffset = tagBeginOffset + zone.Size; } if (WriteMode.REPLACE == writeResult.RequiredMode) { // Need to build a larger file if (newTagSize > zone.Size) { if (!buffered) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation : Lengthening (delta=" + (newTagSize - zone.Size) + ")"); } StreamUtils.LengthenStream(w.BaseStream, tagEndOffset, (uint)(newTagSize - zone.Size)); } else if (newTagSize < zone.Size) // Need to reduce file size { if (!buffered) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation : Shortening (delta=" + (newTagSize - zone.Size) + ")"); } StreamUtils.ShortenStream(w.BaseStream, tagEndOffset, (uint)(zone.Size - newTagSize)); } } // Copy tag contents to the new slot w.BaseStream.Seek(tagBeginOffset, SeekOrigin.Begin); s.Seek(0, SeekOrigin.Begin); if (writeResult.WrittenFields > 0) { StreamUtils.CopyStream(s, w.BaseStream); } else { if (zone.CoreSignature.Length > 0) { msw.Write(zone.CoreSignature); } } long delta = newTagSize - oldTagSize; cumulativeDelta += delta; // Edit wrapping size markers and frame counters if needed if (structureHelper != null && (MetaDataIOFactory.TAG_NATIVE == implementedTagType || (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2))) { ACTION action; bool isTagWritten = (writeResult.WrittenFields > 0); if (0 == delta) { action = ACTION.Edit; // Zone content has not changed; headers might need to be rewritten (e.g. offset changed) } else { if (oldTagSize == zone.CoreSignature.Length && isTagWritten) { action = ACTION.Add; } else if (newTagSize == zone.CoreSignature.Length && !isTagWritten) { action = ACTION.Delete; } else { action = ACTION.Edit; } } result = structureHelper.RewriteHeaders(w, delta, action, zone.Name, globalOffsetCorrection); } zone.Size = (int)newTagSize; } // if (writeProgress != null) writeProgress.Report(++currentProgress / totalProgressSteps); } // Loop through zones return(result); }
/// <summary> /// Rewrites zones that have to be rewritten /// - Works region after region, buffering them if needed /// - Put each zone into memory and update them using the given WriteDelegate /// - Adjust file size and region headers accordingly /// </summary> /// <param name="fullScopeWriter">BinaryWriter opened on the data stream (usually, contents of an audio file) to be rewritten</param> /// <param name="write">Delegate to the write method of the <see cref="IMetaDataIO"/> to be used to update the data stream</param> /// <param name="zones">Zones to rewrite</param> /// <param name="dataToWrite">Metadata to update the zones with</param> /// <param name="tagExists">True if the tag already exists on the current data stream; false if not</param> /// <param name="useBuffer">True if I/O has to be buffered. Makes I/O faster but consumes more RAM.</param> /// <returns>True if the operation succeeded; false if it something unexpected happened during the processing</returns> private bool RewriteZones( BinaryWriter fullScopeWriter, WriteDelegate write, ICollection <Zone> zones, TagData dataToWrite, bool tagExists, bool useBuffer) { long oldTagSize; long newTagSize; long globalOffsetCorrection; long globalCumulativeDelta = 0; bool result = true; bool isBuffered = false; IList <ZoneRegion> zoneRegions = computeZoneRegions(zones, fullScopeWriter.BaseStream.Length); BinaryWriter writer; Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "========================================"); Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Found " + zoneRegions.Count + " regions"); foreach (ZoneRegion region in zoneRegions) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, region.ToString()); } Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "========================================"); int regionIndex = 0; foreach (ZoneRegion region in zoneRegions) { long regionCumulativeDelta = 0; Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "------------ REGION " + regionIndex++); int initialBufferSize = region.Size; MemoryStream buffer = null; try { if (useBuffer && region.IsBufferable) { isBuffered = true; Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Buffering " + Utils.GetBytesReadable(initialBufferSize)); buffer = new MemoryStream(initialBufferSize); // Copy file data to buffer if (initialBufferSize > 0) { if (structureHelper != null) { fullScopeWriter.BaseStream.Seek(structureHelper.getCorrectedOffset(region.StartOffset), SeekOrigin.Begin); } else // for classes that don't use FileStructureHelper(FLAC) { fullScopeWriter.BaseStream.Seek(region.StartOffset + globalCumulativeDelta, SeekOrigin.Begin); } StreamUtils.CopyStream(fullScopeWriter.BaseStream, buffer, initialBufferSize); } writer = new BinaryWriter(buffer, Settings.DefaultTextEncoding); globalOffsetCorrection = region.StartOffset; } else { isBuffered = false; writer = fullScopeWriter; globalOffsetCorrection = 0; } foreach (Zone zone in region.Zones) { oldTagSize = zone.Size; Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "------ ZONE " + zone.Name + "@" + zone.Offset); Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Allocating " + Utils.GetBytesReadable(zone.Size)); // Write new tag to a MemoryStream using (MemoryStream s = new MemoryStream(zone.Size)) using (BinaryWriter msw = new BinaryWriter(s, Settings.DefaultTextEncoding)) { // DataSizeDelta needs to be incremented to be used by classes that don't use FileStructureHelper (e.g. FLAC) dataToWrite.DataSizeDelta = globalCumulativeDelta; WriteResult writeResult = write(msw, dataToWrite, zone); if (WriteMode.REPLACE == writeResult.RequiredMode) { if (writeResult.WrittenFields > 0) { newTagSize = s.Length; if (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2 && embedder.ID3v2EmbeddingHeaderSize > 0) { StreamUtils.LengthenStream(s, 0, embedder.ID3v2EmbeddingHeaderSize); s.Position = 0; embedder.WriteID3v2EmbeddingHeader(msw, newTagSize); newTagSize = s.Length; } } else { newTagSize = zone.CoreSignature.Length; } } else // Overwrite mode { newTagSize = zone.Size; } Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "newTagSize : " + Utils.GetBytesReadable(newTagSize)); // -- Adjust tag slot to new size in file -- long tagBeginOffset, tagEndOffset; if (tagExists && zone.Size > zone.CoreSignature.Length) // An existing tag has been reprocessed { tagBeginOffset = zone.Offset + (isBuffered ? regionCumulativeDelta : globalCumulativeDelta) - globalOffsetCorrection; tagEndOffset = tagBeginOffset + zone.Size; } else // A brand new tag has been added to the file { if (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2) { tagBeginOffset = embedder.Id3v2Zone.Offset - globalOffsetCorrection; } else { switch (defaultTagOffset) { case MetaDataIO.TO_EOF: tagBeginOffset = writer.BaseStream.Length; break; case MetaDataIO.TO_BOF: tagBeginOffset = 0; break; case MetaDataIO.TO_BUILTIN: tagBeginOffset = zone.Offset + (isBuffered ? regionCumulativeDelta : globalCumulativeDelta); break; default: tagBeginOffset = -1; break; } tagBeginOffset -= globalOffsetCorrection; } tagEndOffset = tagBeginOffset + zone.Size; } if (WriteMode.REPLACE == writeResult.RequiredMode) { // Need to build a larger file if (newTagSize > zone.Size) { uint deltaBytes = (uint)(newTagSize - zone.Size); if (!useBuffer) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation (direct) : Lengthening (delta=" + Utils.GetBytesReadable(deltaBytes) + ")"); } else { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Buffer stream operation : Lengthening (delta=" + Utils.GetBytesReadable(deltaBytes) + ")"); } StreamUtils.LengthenStream(writer.BaseStream, tagEndOffset, deltaBytes); } else if (newTagSize < zone.Size) // Need to reduce file size { uint deltaBytes = (uint)(zone.Size - newTagSize); if (!useBuffer) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation (direct) : Shortening (delta=-" + Utils.GetBytesReadable(deltaBytes) + ")"); } else { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Buffer stream operation : Shortening (delta=-" + Utils.GetBytesReadable(deltaBytes) + ")"); } StreamUtils.ShortenStream(writer.BaseStream, tagEndOffset, deltaBytes); } } // Copy tag contents to the new slot writer.BaseStream.Seek(tagBeginOffset, SeekOrigin.Begin); s.Seek(0, SeekOrigin.Begin); if (writeResult.WrittenFields > 0) { StreamUtils.CopyStream(s, writer.BaseStream); } else { if (zone.CoreSignature.Length > 0) { msw.Write(zone.CoreSignature); } } long delta = newTagSize - oldTagSize; regionCumulativeDelta += delta; globalCumulativeDelta += delta; // Edit wrapping size markers and frame counters if needed if (structureHelper != null && (MetaDataIOFactory.TAG_NATIVE == implementedTagType || (embedder != null && implementedTagType == MetaDataIOFactory.TAG_ID3V2))) { ACTION action; bool isTagWritten = (writeResult.WrittenFields > 0); if (0 == delta) { action = ACTION.Edit; // Zone content has not changed; headers might need to be rewritten (e.g. offset changed) } else { if (oldTagSize == zone.CoreSignature.Length && isTagWritten) { action = ACTION.Add; } else if (newTagSize == zone.CoreSignature.Length && !isTagWritten) { action = ACTION.Delete; } else { action = ACTION.Edit; } } // Use plain writer here on purpose because its zone contains headers for the zones adressed by the static writer result &= structureHelper.RewriteHeaders(fullScopeWriter, isBuffered ? writer : null, delta, action, zone.Name, globalOffsetCorrection, isBuffered ? region.Id : -1); } zone.Size = (int)newTagSize; }// MemoryStream used to process current zone } // Loop through zones if (buffer != null) { // -- Adjust file slot to new size of buffer -- long tagEndOffset = region.StartOffset + initialBufferSize; // Need to build a larger file if (buffer.Length > initialBufferSize) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation (buffer) : Lengthening (delta=" + Utils.GetBytesReadable(buffer.Length - initialBufferSize) + ")"); StreamUtils.LengthenStream(fullScopeWriter.BaseStream, tagEndOffset, (uint)(buffer.Length - initialBufferSize)); } else if (buffer.Length < initialBufferSize) // Need to reduce file size { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Disk stream operation (buffer) : Shortening (delta=" + Utils.GetBytesReadable(buffer.Length - initialBufferSize) + ")"); StreamUtils.ShortenStream(fullScopeWriter.BaseStream, tagEndOffset, (uint)(initialBufferSize - buffer.Length)); } // Copy tag contents to the new slot fullScopeWriter.BaseStream.Seek(region.StartOffset, SeekOrigin.Begin); buffer.Seek(0, SeekOrigin.Begin); StreamUtils.CopyStream(buffer, fullScopeWriter.BaseStream); } } finally // Make sure buffers are properly disallocated { if (buffer != null) { buffer.Close(); buffer = null; } } Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, ""); } // Loop through zone regions // Post-processing changes if (structureHelper != null && structureHelper.ZoneNames.Contains(FileStructureHelper.POST_PROCESSING_ZONE_NAME)) { Logging.LogDelegator.GetLogDelegate()(Logging.Log.LV_DEBUG, "Post-processing"); structureHelper.PostProcessing(fullScopeWriter); } return(result); }