Fix Ogg Files Corrupted By Easytag

Last week, I noticed that some of my music files would no longer play in Clementine or Rhythmbox (both of which use Gstreamer). I got the error below when I attempted to play the files:

Internal data stream error.

When I checked the affected files, all of which were Oggs, with oggz-validate (part of the package oggz-tools), I saw errors like this:

-00:00:00.002: serialno 1824847231: Packet out of order (previous 00:00:00.564)
-00:00:00.002: serialno 1824847231: Granulepos decreasing within track

Ogginfo (in the package vorbis-tools) reported the error as follows:

Negative or zero granulepos (-104) on Vorbis stream outside of headers. This file was created by a buggy encoder

I knew I had played the affected files successfully before, so it was clear something I had done recently caused the problem. Easytag was the answer.

I’ve used Easytag for years, and, until now, was pleased with how well it worked. A couple of weeks ago, I used Easytag 2.4.3 to edit the tags on a number of Ogg files in my music library. Although not all the files I edited were corrupted, all of the files that were corrupted had been edited with Easytag.

A search revealed that I wasn’t alone. Bug 776110 – Vorbis file corrupted: “Negative or zero granulepos (-104) on Vorbis stream outside of headers” was reported for Easytag 2.4.3 by Tristan Miller  in December of 2016! The sad thing is that even though the bug was reported over a year ago, as of this writing there is no fix, and there hasn’t been a new release of Easytag since 12/5/2016.

I didn’t want to have to re-rip all the albums that were corrupted; in fact, some of the albums had been converted to digital format from vinyl, and are not available as CDs. I no longer have the equipment to convert them. Neither did I want to lose all the work I’d done tagging the files.

I ended up leveraging some commonly available Linux packages to write two bash scripts, the first, oggcheck.sh, will check an entire music library and list all the artists, albums, and songs that contain corrupted Ogg files. The second, oggfix.sh, attempts to repair the corrupted Ogg files by decoding them to Wav files, then re-encoding them as Oggs. Metadata tags that were written to the original file are preserved.

Quality of Repaired Files

I’m no audio expert, and though I did my best to maintain the quality of the original, corrupted Ogg files by encoding them with their original bitrate from the Wav files, some quality may be lost. The files I repaired sound just fine to me, but it’s possible that someone with better audio equipment or better hearing might be able to tell the difference. I strongly encourage you to keep a backup in case you aren’t happy with the sound quality of the repaired files. A better fix may come along.

Check Your Library

The first script, oggcheck.sh, looks recursively through a music library organized by artist, album, and song, checking each Ogg file to see if it’s corrupted.

For example, if your music library is in a folder called “Music,” which contains a folder for each artist, which contains folders for each album, all you’d need to do is open a Terminal session, navigate to the “Music” folder, and run the script.

Here’s the oggcheck.sh script:

#!/bin/bash
# This script is designed to use oggz-tools to detect corrupt/invalid OGG files.

# Check to make sure oggz-tools is installed.
command -v oggz-validate >/dev/null 2>&1 || { echo >&2 "The package oggz-tools is required, but it is not installed. Aborting."; exit 1; }

# Get the path to the current directory.
path=`pwd`/;

# Create your output function to send the output to the terminal or to a file on the Desktop called oggfix_log.txt.
output () {
	if [ "$tofile" == 'y' ]; then
		echo "$1" >> ~/Desktop/oggcheck.log
	else
		echo "$1"
	fi
}

echo "Do you want to send the results to a file on your Desktop? (y or n, default=n)"
read tofile
if [ -z "$tofile" ]; then
	tofile='n'
fi
output "The following songs return errors when tested with oggz-validate:"

# Loop through the artists.
for artist in *;
do
	# Set a flag used to determine if you need to output an artist and album or not.
	outputartist=false
	outputalbum=false

	if [[ -d "$artist" ]]; then
		# List the artist name.
		#output "$artist"
		for album in $path"$artist"/*/; do
			#output "$album"
			# Check to make sure there are OGGs in the album.
			#output "$album"
			count=`ls -1 "$album"*.ogg 2>/dev/null | wc -l`
			if [ $count != 0 ]; then
				if [[ -d "$album" ]]; then
					# Go through the OGG files.
					for fname in "$album"*.ogg; do
						# Check each OGG file, and only include them in output if they are invalid. You suppressed the output of the oggz-validate command and set it to a variable "validate" to check.    
						validate="$((oggz-validate "$fname") 2>&1)"
						# Only output the song if it is invalid.
						if [ -n "$validate" ]; then
							# Only output the artist and album if they haven't been output yet.
							if [ "$outputartist" = false ]; then
								output "$artist"
								outputartist=true
							fi
							if [ "$outputalbum" = false ]; then
								# List the album name.
								string='  '$(basename "$album")
								output "$string"
								outputalbum=true
							fi
							string='    '$(basename "$fname")
							output "$string"
						fi
					done
				fi
			fi
			outputalbum=false
		done
	fi
done
echo "Done."

The script will ask if you want to send the output to a file on your Desktop named “oggcheck.log,” or send it to stdout. If you have a lot of music, I recommend you elect to send the output to a file.

The script uses oggz-validate to determine if a file is corrupt. If it finds a corrupt Ogg, it will output the artist name, album name, and the corrupt file name.

Fixing the Files

Finding the corrupted files is great, and it’s a good idea to save the oggcheck.log file so that you can see which files have been altered when you fix them with the next script, oggfix.sh.

Oggfix.sh uses the same method of identifying corrupted Ogg files, oggz-validate, that oggcheck.sh does. Instead of just printing the names of affected files, oggfix.sh attempts to repair them. The process oggfix.sh uses is as follows:

  1. Detect the corrupt Ogg file.
  2. Save all the metadata tags, including tagged album art and acoustic ID fingerprint, if any.
  3. Get the bitrate of the original recording.
  4. Decode the original Ogg file, creating a Wav file.
  5. Delete the original Ogg file.
  6. Encode the Wav file to an Ogg using the bitrate of the original Ogg.
  7. Write the metadata tags to the newly encoded Ogg file.
  8. Delete the Wav and tag files.

I’m not an expert with audio files, but in my testing, the repaired Ogg files sound fine, are the same size as the originals, and retain all the tags I painstakingly added with Easytag, so it seems like a good fix.

Here is the script oggfix.sh:

#!/bin/bash
# This script is designed to use oggz-tools to detect corrupt/invalid OGG files and to fix them by saving the tags--if any--decoding, then re-encoding the file, and writing the tags back to the file.

# Make sure they know what they're doing.
echo "Make sure you have a backup! This script will delete the original files after re-encoding them. Are you sure you want to continue? (y or n, default=n)"
read doit
if [ -z "$doit" ]; then
	doit='n'
fi
if [ "$doit" != 'y' ]; then
	echo "Aborting script...no files altered."
	exit 1
fi

# Get the path to the current directory.
path=`pwd`/;

# Create your output function to send the output to the terminal or to a file on the Desktop called oggfix_log.txt.
output () {
	printf "%s\n" "$1"
}

# Check to make sure the required packages are installed.
output "Checking for required packages..."
command -v oggz-validate >/dev/null 2>&1 || { echo >&2 "The package oggz-tools is required, but it is not installed. Aborting."; exit 1; }
command -v oggdec >/dev/null 2>&1 || { echo >&2 "The package vorbis-tools or a package containing oggdec is required, but is not installed. Aborting."; exit 1; }
command -v oggenc >/dev/null 2>&1 || { echo >&2 "The package vorbis-tools or a package containing oggenc is required, but is not installed. Aborting."; exit 1; }
command -v vorbiscomment >/dev/null 2>&1 || { echo >&2 "The package vorbis-tools or a package containing vorbiscomment is required, but is not installed. Aborting."; exit 1; }
output "All required packages found, beginning search for invalid OGG files..."

# Loop through the artists.
for artist in *;
do
	# Set a flag used to determine if you need to output an artist and album or not.
	outputartist=false
	outputalbum=false

	if [[ -d "$artist" ]]; then
		# List the artist name.
		#output "$artist"
		for album in $path"$artist"/*/; do
			#output "$album"
			# Check to make sure there are OGGs in the album.
			#output "$album"
			count=`ls -1 "$album"*.ogg 2>/dev/null | wc -l`
			if [ $count != 0 ]; then
				if [[ -d "$album" ]]; then
					# Go through the OGG files.
					for fname in "$album"*.ogg; do
						# Check each OGG file, and only include them in output if they are invalid. You suppressed the output of the oggz-validate command and set it to a variable "validate" to check.    
						validate="$((oggz-validate "$fname") 2>&1)"
						# Only output the song if it is invalid.
						if [ -n "$validate" ]; then
							# Only output the artist and album if they haven't been output yet.
							if [ "$outputartist" = false ]; then
								output "=============================="
								output "$artist"
								output "=============================="
								outputartist=true
							fi
							if [ "$outputalbum" = false ]; then
								# List the album name.
								string=$(basename "$album")
								output "---------------------------------------"
								output "$string"
								output "---------------------------------------"
								outputalbum=true
							fi
							# Get the song name for use in output.
							oggname=$(basename "$fname")
							# First, save the tags from the original file:
							output "Saving metadata from: $oggname..."
							vorbiscomment -l "$fname" >> "$album"tags
							# Try to get the bitrate of the original file. Note that this depends on ogginfo not changing the way it outputs the "Nominal bitrate". If it fails, that might be the cause.
							info=$((ogginfo "$fname") 2>&1)
							# Get the next-to-last field on the line with "Nominal bitrate" on it, which should give you the number without the units.
							bitrate=$(echo "$info" | awk '/^Nominal bitrate/{print $(NF - 1)}')
							# Round off the bitrate to a whole number.
							bitrate=$(printf '%.0f\n' $bitrate)
							# Next, decode the original ogg file, creating a wav file, and in the process, losing all the tags.
							oggdec "$fname"
							# Save the name of the wav to a new variable.
							wav="${fname%.*}.wav"
							wavname=$(basename "$wav")
							# Delete the original ogg file.
							output "Deleting $oggname..."
							rm "$fname"
							# Now, re-encode the wav file as an ogg, using the bitrate of the original file, if you got one.
							# Set a regular expression to check for integer values.
							re='^[0-9]+$'
							if ! [[ $bitrate =~ $re ]]; then
								# In this case, bitrate is not an integer, so set a default.
								bitrate=160
							fi
							oggenc -b $bitrate "$wav"
							# Delete the wav file.
							output "Deleting $wavname..."
							rm "$wav"
							# Next, write the tags back to the new ogg file.
							vorbiscomment -w -c "$album"tags "$fname"
							# Delete the tags file.
							output "Deleting the tags file..."
							rm "$album"tags
							output "Done processing $oggname."
						fi
					done
					output "---------------------------------------"
				fi
			fi
			outputalbum=false
		done
	fi
done
echo "Done."

Making It Easy

In order to make things easy, I like to add scripts like the above to a “Scripts” folder in my home directory and add the “Scripts” folder to my path so that all I have to do in order to execute one of my scripts is to type the name of the script at the command line and hit Enter. In case you’re new to bash scripts, this is how you’d set up the two scripts to run on your Linux box:

  1. Copy the two scripts, oggcheck.sh and oggfix.sh above, and save them as oggcheck.sh and oggfix.sh to your Scripts folder using your favorite plain-text editor. If you don’t already have a Scripts folder, create one in your home folder.
  2. Open a terminal and cd to the Scripts folder:
    cd ~/Scripts
    
  3. Make the two scripts executable:
    chmod +x oggcheck.sh oggfix.sh
    
  4. Open your ~/.bashrc file in order to add the Scripts folder to your path:
    sudo nano ~/.bashrc
    
  5. Add the following to the end of your ~/.bashrc file.
    # Add extra paths.
    export PATH=$PATH:~/Scripts
    
  6. Save the changes to your ~/.bashrc file by pressing Ctrl+O and exit nano with Ctrl+X.
  7. Update the path without rebooting:
    source ~/.bashrc
    

Now, you’re ready to run the scripts on your music library.

YMMV

I’m providing the scripts above in the hope that they will save others whose Ogg files have been corrupted by Easytag from having to recreate their music libraries. The scripts are provided with no warranty or support. I strongly encourage you to do a full backup of your music library before trying them, and to keep the oggcheck.log file so that you know exactly which files are altered if you run oggfix.sh.

I tested the scripts above on my music library using an Ubuntu MATE 17.10 box. If you’re using a different Linux distro, you may need to install some packages in order for them to work. I added some code in the scripts to check for the required packages and to exit without making any changes if the required packages aren’t found.