Skip to content

Commit

Permalink
1.5.0 error and stability update
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurBeaulieu committed Sep 11, 2020
1 parent 4c1c594 commit 657634c
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 34 deletions.
6 changes: 4 additions & 2 deletions OstrichRemover.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from src.utils.tools import *
# Globals
global scriptVersion
scriptVersion = '1.4.7'
scriptVersion = '1.5.0'


# Script main frame
Expand Down Expand Up @@ -157,13 +157,15 @@ def fillTags(args):
if len(path) == 2 and path[1] != '':
albumFiller = AlbumFiller(files, preservedPath, args['verbose'], args['errors'])
albumFillers.append(albumFiller)
filledTracks += albumFiller.album.totalTrack
if albumFiller.hasErrors is False:
filledTracks += albumFiller.album.totalTrack
# Display a progress every step %
fillPercentage = (filledTracks * 100) / totalTracks
if totalTracks > 10 and fillPercentage >= step and filledTracks < totalTracks:
if (filledTracks * 100) / totalTracks > percentage and percentage < 100:
printFillProgress(percentage, filledTracks)
percentage += step
# Couldn't fill all track because of naming error
if totalTracks is not filledTracks:
printInvalidFolderStructure(filledTracks, totalTracks, 'fill')
# In this case, ui has display a percentage progression. No need to add a line break if no progression is to be displayed
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OstrichRemover

![](https://badgen.net/badge/version/1.4.7/blue) ![](https://badgen.net/badge/license/GPL-3.0/green)
![](https://badgen.net/badge/version/1.5.0/blue) ![](https://badgen.net/badge/license/GPL-3.0/green)

##### Like your audio files to be correctly tagged ? *OstrichRemover* might help you !

Expand All @@ -14,7 +14,7 @@ This script uses `Python3`, and requires `mutagen`, `Pillow` (that replaced Pyth

`# pip install -r requierements.txt`

When all requierements are installed, you can launch *OstrichRemover* in three main modes, and on additional command :
When all requierements are installed, you can launch *OstrichRemover* in three main modes, and one additional command :

## Scan mode (`-s` or `--scan`)

Expand All @@ -23,7 +23,7 @@ Available options :
- `-v` or `--verbose` for a verbose output.

The script will crawl the folder you gave as an argument and will report you any error it found in your file naming / tagging. If specified with a `-d` of `--dump` flag, errors can be outputed in a JSON file, to be further reviewed in the `web-report/index.html` file (just drag and drop the json file in the input area).
*OstrichRemover* can detect **37 errors** per file (so far). Those errors are grouped in four categories that are detailed [in the wiki](https://github.com/ArthurBeaulieu/OstrichRemover/wiki/Tracked-Errors), respectively:
*OstrichRemover* can detect **39 errors** per file (so far). Those errors are grouped in four categories that are detailed [in the wiki](https://github.com/ArthurBeaulieu/OstrichRemover/wiki/Tracked-Errors), respectively:

- *Category 1* – File system naming inconsistencies ;
- *Category 2* – File system naming against ID3 tags ;
Expand Down Expand Up @@ -101,7 +101,6 @@ The script will crawl the folder you gave as an argument, to clean all existing

##### v3.0
- [ ] Packaging in pip (PyPi)
- [ ] Verbose option (unified)
- [ ] Qt interface
- [ ] ManaZeak integration as a plugin

Expand Down
4 changes: 4 additions & 0 deletions src/fill/albumFiller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from src.models.album import Album
from src.models.track import Track


class AlbumFiller:
def __init__(self, files, preservedPath, verbose, logErrors):
self.preservedPath = preservedPath
self.files = files
self.album = Album(files)
self.verbose = verbose
self.logErrors = logErrors
self.hasErrors = False
self._analyseAlbumInternals()
self._analyseTracks()

Expand All @@ -34,13 +36,15 @@ def _analyseAlbumInternals(self):
if int(fileNameList[len(fileNameList) - 3][:-2]) > int(self.album.totalDisc):
self.album.totalDisc = fileNameList[len(fileNameList) - 3][:-2]
except:
self.hasErrors = True
if self.verbose == True or self.logErrors == True:
print("ERROR for track : {}\n\tThe file isn't named according to the naming convention.\n".format(fileName))
if self.album.year == 0:
self.album.year = fileNameList[1]
if self.verbose:
print('Track {}: {}\n\tRelease artist: {}\n\tAlbum: {}'.format(fileNameList[3], fileNameList[5][:-5], fileNameList[0], fileNameList[2]))
else:
self.hasErrors = True
if self.verbose == True or self.logErrors == True:
print("ERROR for track : {}\n\tThe file isn't named according to the naming convention.\n".format(fileName))
if fileName[-3:] == 'JPG' or fileName[-3:] == 'jpg' or fileName[-4:] == 'JPEG' or fileName[-4:] == 'jpeg' or fileName[-3:] == 'PNG' or fileName[-3:] == 'png':
Expand Down
21 changes: 19 additions & 2 deletions src/models/track.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Python imports
import mimetypes
import PIL
# Project imports
from mutagen.id3 import ID3
from mutagen.flac import FLAC, Picture
from mutagen.id3._frames import TIT2, TDRC, TPE1, TPE2, TOPE, TRCK, TALB, TCMP, TCOM, TPOS, APIC

import mimetypes
import PIL

# from utils.uiBuilder import printDetailledTrack # Uncomment for debug purpose only (printDetailledTrack() is very verbose)
mimetypes.init()
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(self, fileType, pathList, fileName, audioTagPath):
self._fillFromFLAC()
self._computeInternals()


# Read the mp3 track ID3 tags and extract all interresting values into a Track object
def _fillFromMP3(self):
if 'TIT2' in self.audioTag and self.audioTag['TIT2'].text[0] != '':
Expand Down Expand Up @@ -102,6 +104,7 @@ def _fillFromMP3(self):
if 'TDOR' in self.audioTag and self.audioTag['TDOR'].text[0] != '':
self.date = str(self.audioTag['TDOR'].text[0])


# Read the flac track Vorbis tags and extract all interresting values into a Track object
def _fillFromFLAC(self):
if 'TITLE' in self.audioTag:
Expand Down Expand Up @@ -141,6 +144,7 @@ def _fillFromFLAC(self):
if 'RELEASEDATE' in self.audioTag:
self.date = self.audioTag['RELEASEDATE'][0]


# Compute all class internals that can not be extracted from ID3 tags
def _computeInternals(self):
self._computeFileNameList()
Expand All @@ -149,6 +153,7 @@ def _computeInternals(self):
self._computeRemixer()
self._containsCover()


# Splits the filename into its components
# (%releaseArtists% - %year% - %albumTitle% - %discNumber%%trackNumber% - %artists% - %title%)
def _computeFileNameList(self):
Expand All @@ -160,6 +165,7 @@ def _computeFileNameList(self):
# When album is a single, we must re-join the album name and the 'Single' suffix
self.fileNameList[2:4] = [' - '.join(self.fileNameList[2:4])] # Re-join with a ' - ' separator


# Splits the folderame into its components (%year% - %albumTitle%)
def _computeFolderNameList(self):
# We also split the folder name to make a double check for Year and Album name
Expand All @@ -170,6 +176,7 @@ def _computeFolderNameList(self):
# When album is a single, we must re-join the album name and the 'Single' suffix
self.folderNameList[1:3] = [' - '.join(self.folderNameList[1:3])] # Re-join with a ' - ' separator


# Extract the featured artist(s) name(s) from the track fileName
def _computeFeaturing(self):
if self.fileName.find('(feat.') != -1:
Expand All @@ -182,6 +189,7 @@ def _computeFeaturing(self):
return
self.composedPerformer = self.artists # No featuring so performer should be equal to artist


# Extract the track remix artist name from the track fileName
def _computeRemixer(self):
if self.fileNameList[len(self.fileNameList) - 1].find(' Remix)') != -1:
Expand All @@ -190,6 +198,7 @@ def _computeRemixer(self):
self.fileName.rfind('(', 0, len(self.fileName)) + 1:self.fileName.rfind(' Remix)')
].split(', ')


# Test the cover existence in the file
def _containsCover(self):
# Extract image from file
Expand All @@ -212,6 +221,7 @@ def _containsCover(self):
else:
self.hasCover = False


def testTagsUnicity(self):
if self.fileType == 'MP3':
pass
Expand All @@ -236,6 +246,7 @@ def testTagsUnicity(self):
if 'RELEASEDATE' in self.audioTag and len(self.audioTag['RELEASEDATE']) > 1: return False
return True


# Clear all previously existing tags
def clearInternalTags(self, album):
# We could use audioTag.delete() but we just want to clear the tags supported by convention
Expand Down Expand Up @@ -269,6 +280,7 @@ def clearInternalTags(self, album):
self.audioTag.clear_pictures()
self.audioTag.save(self.audioTagPath)


# Compute all class internals that can not be extracted from ID3 tags
def setInternalTags(self, album):
# Compilation tag is '0' for regular release, '1' for various artist and '2' for mixes
Expand Down Expand Up @@ -332,13 +344,15 @@ def setInternalTags(self, album):
# Now save all the new tags into the audio file
self.audioTag.save(self.audioTagPath)


# Check if the tag is already filled before adding one
def _setInternalTag(self, tag, value):
if tag in self.audioTag and self.audioTag[tag] is not value:
self.audioTag[tag] = value
else:
self.audioTag[tag] = ''


# This method will fill :
# - Label tag if publisher tag was previously filled (according to this convention, the label is stored in publisher (TPUB) for mp3 files)
def _fillTagsFromPreviouslyExistingTag(self):
Expand All @@ -347,6 +361,7 @@ def _fillTagsFromPreviouslyExistingTag(self):
self._setInternalTag('LABEL', self.audioTag['PUBLISHER'][0])
self.audioTag['PUBLISHER'] = '' # Clear publisher tag


# Build artist array from artist string and support remix artist if any
def _buildArtistsList(self):
outputList = []
Expand All @@ -359,6 +374,7 @@ def _buildArtistsList(self):
outputList.sort()
self.artists = outputList


# Build performers array from artist string and support remix artist if any
def _buildPerformersList(self):
outputList = []
Expand All @@ -373,6 +389,7 @@ def _buildPerformersList(self):
outputList.sort()
self.performers = outputList


# Append a cover to the track only if it is 1k by 1k and if there is not any cover
def _addCoverToFile(self, album):
if self.fileType == 'FLAC':
Expand Down
1 change: 0 additions & 1 deletion src/references/refCountry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class RefCountry(object):

# https://en.wikipedia.org/wiki/List_of_NATO_country_codes + BZH (29 rpz)
countryList = ['ATG', 'AFG', 'DZA', 'AZE', 'ALB', 'ARM', 'AND', 'AGO', 'ARG', 'AUS', 'AUT', 'BHR', 'BRB', 'BWA',
'BZH', 'BEL', 'BHS', 'BGD', 'BLZ', 'BIH', 'BOL', 'MMR', 'BEN', 'BLR', 'SLB', 'BRA', 'BTN', 'BGR',
Expand Down
4 changes: 2 additions & 2 deletions src/references/refForbiddenChar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class RefForbiddenChar(object):

# The character that can't be in filenames
forbiddenChars = ['*', '/', '\\', ':', ';', '?', '<', '>', '\"', '|', '\'']
forbiddenChars = ['*', '/', '\\', ':', ';', '?', '<', '>', '\"', '|', '\'']

1 change: 0 additions & 1 deletion src/references/refGenre.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class RefGenre(object):

# Allowed genres and style according to the naming convention
genres = ['2-Step-Garage', 'Abstract', 'Abstract Hip-Hop', 'Acid House', 'Acid Rock', 'Acid Techno', 'Acoustic',
'African Blues', 'Afrobeat', 'Alternative Hip-Hop', 'Alternative Metal',
Expand Down
11 changes: 10 additions & 1 deletion src/scan/albumTester.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Python imports
import os
import fnmatch
# Project imports
from src.models.album import Album
from src.models.track import Track
from src.scan.trackTester import TrackTester
from src.utils.errorEnum import ErrorEnum

import fnmatch,os

# AlbumTester aim to test all tracks in a folder and group all their errors
class AlbumTester:
Expand Down Expand Up @@ -90,8 +92,10 @@ def tracksErrorCounter(self):
errorCounter = 0
labelLockErrors = False
languageLockErrors = False
dateLockErrors = False
albumLabel = self.tracks[0].track.label
albumLanguage = self.tracks[0].track.lang
albumDate = self.tracks[0].track.date
for trackTester in self.tracks:
errorCounter += trackTester.errorCounter
# ErrorCode 30 : Label tag is not consistent over album tracks
Expand All @@ -104,5 +108,10 @@ def tracksErrorCounter(self):
languageLockErrors = True
self.errorCounter += 1
self.errors.append(ErrorEnum.INCONSISTENT_LANGUAGES)
# ErrorCode 38 : Release date is not consistent accross album
if trackTester.track.date != albumDate and dateLockErrors is False:
dateLockErrors = True
self.errorCounter += 1
self.errors.append(ErrorEnum.INCONSISTENT_RELEASE_DATE)
return errorCounter
return 0
17 changes: 13 additions & 4 deletions src/scan/trackTester.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ def _testTrackObject(self):

# Testing Category 1 : Filesystem naming inconsistencies (see ErrorEnum.py)
def _testFileSystemNaming(self):
# TODO test foldername and filename proper length etc
# ErrorCode 00 : Filename release artists doesn't match the artist foldername
self._testErrorForErrorCode(ErrorEnum.FILENAME_RELEASE_ARTIST_VS_ARTIST_FOLDER_NAME, self.track.fileNameList[0],
self.track.pathList[len(self.track.pathList) - 2])
Expand Down Expand Up @@ -105,7 +104,7 @@ def _testTagsInconsistencies(self):
# ErrorCode 23 : BPM is not an integer
# ErrorCode 24 : Release year is not realistic (< 1900 or > today)
# ErrorCode 29 : Invalid compilation tag
self._testIntegerFieldsValidity()
self._testFieldsValidity()
# ErrorCode 25 : Invalid country value. Use NATO country notation with 3 capital letters
# ErrorCode 26 : Unexisting country trigram. Check existing NATO values
self._testLanguageTag()
Expand Down Expand Up @@ -298,8 +297,8 @@ def _testCoverValidity(self):
self.errors.append(ErrorEnum.COVER_DESCRIPTION_NOT_MATCHING)


# Test ID3 fields to check if they are indeed integer (floating are forbidden in those)
def _testIntegerFieldsValidity(self):
# Test ID3 fields to check if they are indeed valid
def _testFieldsValidity(self):
# Prevents any test if year is an empty tag
if len(self.track.year) == 0:
self.errorCounter += 1
Expand All @@ -317,6 +316,16 @@ def _testIntegerFieldsValidity(self):
if self.track.compilation != '0' and self.track.compilation != '1' and self.track.compilation != '2' and self.track.compilation != '3':
self.errorCounter += 1
self.errors.append(ErrorEnum.INVALID_COMPILATION)
# Wrong release date formating (test hyphen, year, month and day)
if self.track.date != '' and (self.track.date[4] != '-' or self.track.date[7] != '-' or len(self.track.date) != 10 or \
int(self.track.date[:4]) < 1900 or int(self.track.date[:4]) > datetime.datetime.now().year or \
int(self.track.date[5:7]) < 1 or int(self.track.date[5:7]) > 12 or \
int(self.track.date[8:]) < 1 or int(self.track.date[8:]) > 31 or \
(int(self.track.date[5:7]) % 2 == 1 and int(self.track.date[8:]) > 31) or \
(int(self.track.date[5:7]) % 2 == 0 and int(self.track.date[8:]) != 2 and int(self.track.date[8:]) > 30) or \
(int(self.track.date[5:7]) == 2 and int(self.track.date[8:]) > 29)):
self.errorCounter += 1
self.errors.append(ErrorEnum.WRONG_DATE_FORMAT)


# Test the lang tag to check its compliance with NATO country trigrams
Expand Down
10 changes: 10 additions & 0 deletions src/utils/errorEnum.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ class ErrorEnum(Enum):
'errorCode': 36,
'errorValue': "Cover description doesn't match cover filename"
}
# ErrorCode 37 : Release date tag is not using YYYY-MM-DD format
WRONG_DATE_FORMAT = {
'errorCode': 37,
'errorValue': "Release date is not a valid date"
}
## ------------
# Category 4 : Track tags coherence with album metrics
# ErrorCode 14 : Computed album total track is not equal to the track total track tag
Expand Down Expand Up @@ -198,3 +203,8 @@ class ErrorEnum(Enum):
'errorCode': 34,
'errorValue': "There is no cover, or there are more than one cover"
}
# ErrorCode 38 : Release date tag is not consistent over album tracks
INCONSISTENT_RELEASE_DATE = {
'errorCode': 38,
'errorValue': "Release date tag is not consistent over album tracks"
}
1 change: 0 additions & 1 deletion src/utils/reportBuilder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Python imports
import datetime
import json

# Project imports
from src.utils.errorEnum import ErrorEnum
from src.utils.tools import createDirectory
Expand Down
1 change: 0 additions & 1 deletion src/utils/tools.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Python imports
import os
import sys

# Project imports
from src.utils.errorEnum import ErrorEnum
from src.references.refForbiddenChar import RefForbiddenChar
Expand Down
Loading

0 comments on commit 657634c

Please sign in to comment.