Encapsulates the 10 byte long header of an ID3 v2 tag.
Beispiel #1
0
        private ID3v2MajorVersion Read(Stream stream, FrameParserFactory frameParserFactory)
        {
            TagHeader header = TagHeader.FromStream(stream);

            if (header == null)
            {
                throw new FatalException("No ID3 v2 tag is attached to this file.");
            }
            if (!Enum.IsDefined(typeof(ID3v2MajorVersion), header.MajorVersion))
            {
                throw new FatalException("Reading this major version of ID3 v2 is not supported.");
            }
            if (header.Flags != TagHeaderFlags.None)
            {
                throw new FatalException("Reading tags with with any set flags are not supported in this version.");
            }
            long startingPosition = stream.Position;

            while (stream.Position - startingPosition < header.TagSize)
            {
                long   beginPosition = stream.Position;
                Frame  frame         = null;
                string frameID       = "";
                try
                {
                    frame = FrameParser.Parse(stream, (ID3v2MajorVersion)header.MajorVersion, frameParserFactory, out frameID);
                    if (frame == null)
                    {
                        break;
                    }
                }
                catch (NonFatalException ex)
                {
                    RaiseReadingWarning(ex, frameID);
                }
                if (beginPosition == stream.Position)              // Probably stuck in an infinite loop because of corrupt data in file. Exit the loop.
                {
                    break;
                }
                if (frame != null)
                {
                    this.Frames.Add(frame);
                }
            }
            return((ID3v2MajorVersion)header.MajorVersion);
        }
Beispiel #2
0
        /// <summary>
        /// Removes any attached ID3v2 tags attached to the given file.
        /// Does not change the ID3v1 tags.
        /// </summary>
        /// <param name="fileName">Full path of the file whose tags are to be removed.</param>
        /// <returns>True if tags were found and successfully removed. False otherwise.</returns>
        public static bool RemoveTag(string fileName)
        {
            FileStream stream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

            try
            {
                TagHeader header = TagHeader.FromStream(stream);
                if (header == null)
                {
                    return(false);
                }
                // An intermediate file needs to be used.
                string     intermediateFile = GetTempIntermediateFileName(fileName);
                FileStream intermedStream   = File.Create(intermediateFile);
                stream.Seek(header.TagSize + 10, SeekOrigin.Begin);

                //Copy the data after the tag to the intermediate file.
                CopyFromStreamToStream(stream, intermedStream);
                intermedStream.Close();
                stream.Close();

                // If control reaches this point, then everything went file, so
                // should normally delete the original file and rename the intermediate file.
                // But as a safety measure, for pre-release, alpha, and beta version,
                // instead of removing the file, I decided to rename the old file to
                // fileName+".old."+revisionNumber instead. The user can manually delete
                // the these files after ensuring the integrity of the new files.
#if ACHAMENES_ID3_BACKUP
                File.Move(fileName, GetBackupFileName(fileName));
#else
                File.Delete(fileName);
#endif
                File.Move(intermediateFile, fileName);

                return(true);
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Reads the ID3 v2 header from a stream.
        /// </summary>
        /// <param name="stream">The stream to read the header from.</param>
        /// <returns>
        /// Returns a TagHeader object with the information
        /// in the header, null if no ID3 v2 tag was available.
        /// </returns>
        /// <remarks>
        /// The method returns null if it can not find a valid ID3 v2
        /// tag in the file. This means that in case of a corrupt
        /// ID3 v2 header, (e.g. invalid tag size), null is returned.
        ///
        /// This method can therefore be used to check a file
        /// for the existence of a valid ID3 v2 tag.
        ///
        /// NOTE: Valid in this context does not mean of compatible version
        /// or having compatible flags.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// The passed stream was null.
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// There was an IO exception while trying to read the header.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The stream does not support reading.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The passed stream was closed before the method was called.
        /// </exception>
        public static TagHeader FromStream(System.IO.Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("The parameter 'stream' can not be null.");
            }

            byte[] data = new byte[10];
            int    read = stream.Read(data, 0, 10);

            if (read != 10)
            {
                return(null);
            }

            if (System.Text.Encoding.GetEncoding("ISO-8859-1").GetString(data, 0, 3) != "ID3")
            {
                return(null);
            }

            int size = 0;

            if (data[6] > 127 || data[7] > 127 || data[8] > 127 || data[9] > 127)       // invalid size
            {
                return(null);
            }

            checked
            {
                try
                {
                    size = data[6] * (1 << 21) + data[7] * (1 << 14) + data[8] * (1 << 7) + data[9];
                }
                catch (OverflowException)                // Invalid size in stream
                {
                    return(null);
                }
            }

            TagHeader header = new TagHeader(data[3], data[4], (TagHeaderFlags)data[5], size);

            return(header);
        }
Beispiel #4
0
		public virtual void WriteToFile(string fileName, ID3v2MajorVersion version, EncodingScheme encoding)
		{
			FileStream targetFileStream=null;
			try
			{
				// First write all frame data to a memory buffer
				// This way we catch all fatal exceptions before 
				// touching the file, so there's not a even a slight 
				// chance of corrupting the file.

				MemoryStream memoryBuffer=new MemoryStream();
				WriteFrames(memoryBuffer, version, encoding);

				int newSize=(int)memoryBuffer.Length;

				if(newSize==0)
				{
					throw new FatalException("No data to write in the tag.");
				}

				targetFileStream=File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
				TagHeader oldHeader=TagHeader.FromStream(targetFileStream);

				bool useIntermediate=false;

				if(oldHeader==null)
				{
					// There is no attached tag in the file. We need to use an intermediate temporary file.					
					useIntermediate=true;
					// Rewind the place in stream to the very beginning, so that when
					// we copy the data from the original file to the intermediate file,
					// we won't lose any data.
					targetFileStream.Seek(0, SeekOrigin.Begin);
				}
				else
				{
					// File already has an ID3 v2 tag attached.
					if(oldHeader.TagSize<newSize)
					{
						useIntermediate=true;
						// Allow for 4KB of padding.
						newSize+=4*1024;
						// Move to the correct place in stream so when we copy data to 
						// the intermediate file, we won't lose any data.
						// The +10 is to go skip the tag header, since it's not included
						// in TagHeader.TagSize 
						targetFileStream.Seek(oldHeader.TagSize+10, SeekOrigin.Begin);
					}
					else
					{
						// We should write exactly the same number of bytes back to the file.
						// When writing the padding, compare memoryBuffer.Length to newSize to
						// calculate the number of padding bytes required to write.
						newSize=oldHeader.TagSize;
						// Seek the beginning of the file. The tag header and frame information
						// will be overwritten.
						targetFileStream.Seek(0, SeekOrigin.Begin);
					}
				}

				TagHeader newHeader=new TagHeader((byte)version, 0, TagHeaderFlags.None, newSize);

				if(useIntermediate)
				{
					string intermediateFileName=GetTempIntermediateFileName(fileName);
					FileStream intermediateStream=null;
					try
					{
						intermediateStream=File.Create(intermediateFileName);
						newHeader.WriteToStream(intermediateStream);

						// Write the frame data residing in memory buffer.
						intermediateStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);

						//Write any required paddings.
						for(int i=(int)memoryBuffer.Length;i<newSize;i++)
						{
							intermediateStream.WriteByte(0);
						}

						// Copy the data from the original file to the intermediate file.
						CopyFromStreamToStream(targetFileStream, intermediateStream);

						// Close the stream of original and intermediate file streams.
						targetFileStream.Close();
						intermediateStream.Close();

						// If control reaches this point, then everything went file, so
						// should normally delete the original file and rename the intermediate file.
						// But as a safety measure, for pre-release, alpha, and beta version, 
						// instead of removing the file, I decided to rename the old file to
						// fileName+".old."+revisionNumber instead. The user can manually delete
						// the these files after ensuring the integrity of the new files.
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
						File.Move(fileName, GetBackupFileName(fileName));
#else
						File.Delete(fileName);
#endif
						File.Move(intermediateFileName, fileName);
					}
					finally
					{
						if(intermediateStream!=null)
						{
							intermediateStream.Close();
						}
					}
				}
				else // If using an intermediate file is not necessary.
				{
					// Similarly, we would normally just start writing to @stream here,
					// but instead, we close it, make a backup copy of it, and then 
					// open it again, and write the tag information just to ensure nothing
					// will be lost.

					targetFileStream.Close();
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
					File.Copy(fileName, GetBackupFileName(fileName));
#endif
					targetFileStream=File.Open(fileName, FileMode.Open, FileAccess.Write, FileShare.Write);
					
					// Write the header.
					newHeader.WriteToStream(targetFileStream);
					
					// Write frame data from memory buffer.
					targetFileStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);
					
					// Write any required padding.
					for(int i=(int)memoryBuffer.Length;i<newSize;i++)
					{
						targetFileStream.WriteByte(0);
					}

					// And finally close the stream.
					targetFileStream.Close();
				}
			}
			finally
			{
				if(targetFileStream!=null)
				{
					targetFileStream.Close();
				}
			}
		}
Beispiel #5
0
        public virtual void WriteToFile(string fileName, ID3v2MajorVersion version, EncodingScheme encoding)
        {
            FileStream targetFileStream = null;

            try {
                // First write all frame data to a memory buffer
                // This way we catch all fatal exceptions before
                // touching the file, so there's not a even a slight
                // chance of corrupting the file.

                MemoryStream memoryBuffer = new MemoryStream();
                WriteFrames(memoryBuffer, version, encoding);

                int newSize = (int)memoryBuffer.Length;

                if (newSize == 0)
                {
                    throw new FatalException("No data to write in the tag.");
                }

                targetFileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                TagHeader oldHeader = TagHeader.FromStream(targetFileStream);

                bool useIntermediate = false;

                if (oldHeader == null)
                {
                    // There is no attached tag in the file. We need to use an intermediate temporary file.
                    useIntermediate = true;
                    // Rewind the place in stream to the very beginning, so that when
                    // we copy the data from the original file to the intermediate file,
                    // we won't lose any data.
                    targetFileStream.Seek(0, SeekOrigin.Begin);
                }
                else
                {
                    // File already has an ID3 v2 tag attached.
                    if (oldHeader.TagSize < newSize)
                    {
                        useIntermediate = true;
                        // Allow for 4KB of padding.
                        newSize += 4 * 1024;
                        // Move to the correct place in stream so when we copy data to
                        // the intermediate file, we won't lose any data.
                        // The +10 is to go skip the tag header, since it's not included
                        // in TagHeader.TagSize
                        targetFileStream.Seek(oldHeader.TagSize + 10, SeekOrigin.Begin);
                    }
                    else
                    {
                        // We should write exactly the same number of bytes back to the file.
                        // When writing the padding, compare memoryBuffer.Length to newSize to
                        // calculate the number of padding bytes required to write.
                        newSize = oldHeader.TagSize;
                        // Seek the beginning of the file. The tag header and frame information
                        // will be overwritten.
                        targetFileStream.Seek(0, SeekOrigin.Begin);
                    }
                }

                TagHeader newHeader = new TagHeader((byte)version, 0, TagHeaderFlags.None, newSize);

                if (useIntermediate)
                {
                    string     intermediateFileName = GetTempIntermediateFileName(fileName);
                    FileStream intermediateStream   = null;
                    try {
                        intermediateStream = File.Create(intermediateFileName);
                        newHeader.WriteToStream(intermediateStream);

                        // Write the frame data residing in memory buffer.
                        intermediateStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);

                        //Write any required paddings.
                        for (int i = (int)memoryBuffer.Length; i < newSize; i++)
                        {
                            intermediateStream.WriteByte(0);
                        }

                        // Copy the data from the original file to the intermediate file.
                        CopyFromStreamToStream(targetFileStream, intermediateStream);

                        // Close the stream of original and intermediate file streams.
                        targetFileStream.Close();
                        intermediateStream.Close();

                        // If control reaches this point, then everything went file, so
                        // should normally delete the original file and rename the intermediate file.
                        // But as a safety measure, for pre-release, alpha, and beta version,
                        // instead of removing the file, I decided to rename the old file to
                        // fileName+".old."+revisionNumber instead. The user can manually delete
                        // the these files after ensuring the integrity of the new files.
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
                        File.Move(fileName, GetBackupFileName(fileName));
#else
                        File.Delete(fileName);
#endif
                        File.Move(intermediateFileName, fileName);
                    }
                    finally {
                        if (intermediateStream != null)
                        {
                            intermediateStream.Close();
                        }
                    }
                }
                else // If using an intermediate file is not necessary.
                {
                    // Similarly, we would normally just start writing to @stream here,
                    // but instead, we close it, make a backup copy of it, and then
                    // open it again, and write the tag information just to ensure nothing
                    // will be lost.

                    targetFileStream.Close();
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
                    File.Copy(fileName, GetBackupFileName(fileName));
#endif
                    targetFileStream = File.Open(fileName, FileMode.Open, FileAccess.Write, FileShare.Write);

                    // Write the header.
                    newHeader.WriteToStream(targetFileStream);

                    // Write frame data from memory buffer.
                    targetFileStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);

                    // Write any required padding.
                    for (int i = (int)memoryBuffer.Length; i < newSize; i++)
                    {
                        targetFileStream.WriteByte(0);
                    }

                    // And finally close the stream.
                    targetFileStream.Close();
                }
            }
            finally {
                if (targetFileStream != null)
                {
                    targetFileStream.Close();
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Reads the ID3 v2 header from a stream.
        /// </summary>
        /// <param name="stream">The stream to read the header from.</param>
        /// <returns>
        /// Returns a TagHeader object with the information 
        /// in the header, null if no ID3 v2 tag was available.
        /// </returns>
        /// <remarks>
        /// The method returns null if it can not find a valid ID3 v2
        /// tag in the file. This means that in case of a corrupt
        /// ID3 v2 header, (e.g. invalid tag size), null is returned.
        /// 
        /// This method can therefore be used to check a file
        /// for the existence of a valid ID3 v2 tag.
        /// 
        /// NOTE: Valid in this context does not mean of compatible version
        /// or having compatible flags.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// The passed stream was null.
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// There was an IO exception while trying to read the header.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// The stream does not support reading.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The passed stream was closed before the method was called.
        /// </exception>
        public static TagHeader FromStream(System.IO.Stream stream)
        {
            if(stream==null)
                throw new ArgumentNullException("The parameter 'stream' can not be null.");

            byte[] data=new byte[10];
            int read=stream.Read(data, 0, 10);

            if(read!=10)
                return null;

            if(System.Text.Encoding.GetEncoding("ISO-8859-1").GetString(data, 0, 3)!="ID3")
                return null;

            int size=0;

            if(data[6]>127 || data[7] > 127 || data[8]>127 || data[9]> 127) // invalid size
                return null;

            checked
            {
                try
                {
                    size=data[6]*(1<<21)+data[7]*(1<<14)+data[8]*(1<<7)+data[9];
                }
                catch(OverflowException) // Invalid size in stream
                {
                    return null;
                }
            }

            TagHeader header=new TagHeader(data[3], data[4], (TagHeaderFlags)data[5], size);

            return header;
        }