# .opus scanner?

**URL:** <https://forums.plex.tv/t/opus-scanner/103429>\
**Category:** Dev/API Corner\
**Tags:** scanner-agent-dev\
**Created:** [May 18, 2015, 4:40am UTC](https://forums.plex.tv/t/opus-scanner/103429 "2015-05-18T04:40:08Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![atomicxblue](https://avatars.discourse-cdn.com/v4/letter/a/f1d935/32.png) [@atomicxblue](https://forums.plex.tv/u/atomicxblue)\
**Post date:** [May 18, 2015, 4:40am UTC](https://forums.plex.tv/t/opus-scanner/103429/1 "2015-05-18T04:40:08Z")

</div>

Does anyone know of a scanner that works for .opus files? I have quite a few that I want to add to my Library, but plex has been fighting me at every turn. I know you can add a new audio\_ext to AudioFiles.py, but this soon gets overwritten. When it is able to pull them in, it refuses to even read the metadata that I spent so long making sure was right. This is a regular ogg file, so it should just be able to use the same setup. (Which doesn't work, sadly...)

&nbsp;

I threw together some code that shows it should be possible to extra the metadata info from .opus files with mutagen. I got the idea because there's already an oggopus.py parser included with the install, but no clue how to add this into a custom scanner. Any help would be greatly appreciated!

&nbsp;

(Btw, put this code in a directory with .opus files)

```
#!/usr/bin/env python

import os, sys

from mutagen import File as MFile  
#import mutagen.mp3  
#import mutagen.id3  
#import mutagen.flac  
#import mutagen.oggvorbis  
#import mutagen.oggopus

TYPES = {  
‘mp3’: ‘MP3’,  
‘aac’: ‘AAC’,  
‘alac’: ‘ALAC’,  
‘ogg’: ‘OGG’,  
‘opus’: ‘Opus’,  
‘flac’: ‘FLAC’,  
‘ape’: ‘APE’,  
‘wv’: ‘WavPack’,  
‘mpc’: ‘Musepack’,  
‘asf’: ‘Windows Media’,  
‘aiff’: ‘AIFF’,  
}

workingDirectory = “./”

fileListMetadata =

# Get list of files in directory

fileList = os.listdir(workingDirectory)

# Clean up fileList to be in order

fileList.sort()

# Scan through the fileList and remove those which are not audio files

for filename in fileList:  
Metadata =   
tag = MFile(filename, None, True)  
file, fileExtension = os.path.splitext(filename) # read file extensions  
if fileExtension[1:] not in TYPES: # clean out files that are not audio files  
fileList.remove(filename) # if it’s not an audio file, we don’t want it  
else:  
if type(tag). **name** is ‘OggOpus’:  
# Extract metadata  
artist = (str(MFile(filename, None, True)[‘ARTIST’])) # might need [3:-2] to clean up tags??  
album = (str(MFile(filename, None, True)[‘ALBUM’]))  
title = (str(MFile(filename, None, True)[‘TITLE’]))  
track = (str(MFile(filename, None, True)[‘TRACKNUMBER’]))  
disc = (str(MFile(filename, None, True)[‘DISCNUMBER’]))  
TPE2 = (str(MFile(filename, None, True)[‘ALBUMARTIST’]))

```
        Metadata.append(filename)
        Metadata.append(artist)
        Metadata.append(album)
        Metadata.append(title)
        Metadata.append(track)
        Metadata.append(disc)
        Metadata.append(TPE2)
        
# Add the set to the list of metadata
fileListMetadata.append(Metadata)

```

print fileListMetadata
```

---

<div class="post-metadata">

**Author:** ![ZeroQI](https://sea1.discourse-cdn.com/plex/user_avatar/forums.plex.tv/zeroqi/32/6522_2.png) [@ZeroQI](https://forums.plex.tv/u/ZeroQI)\
**Post date:** [December 12, 2016, 7:29am UTC](https://forums.plex.tv/t/opus-scanner/103429/2 "2016-12-12T07:29:34Z")

</div>

Don’t have much time to code it and maintain but thought i would give pointers. the code above should allow for a quick hack of existing music scanners easilly

- Codec info [https://forums.plex.tv/discussion/70734/opus-audio-codec](https://forums.plex.tv/discussion/70734/opus-audio-codec)
- Plex music Scanner source: [https://github.com/plexinc-agents/PlexMusic.bundle/blob/master/Contents/Code/](https://github.com/plexinc-agents/PlexMusic.bundle/blob/master/Contents/Code/) **init**.py and [https://github.com/plexinc-plugins/Scanners.bundle/blob/master/Contents/Resources/Common/AudioFiles.py](https://github.com/plexinc-plugins/Scanners.bundle/blob/master/Contents/Resources/Common/AudioFiles.py)
- Plex better itunes scanner (tiny): [https://github.com/charliesome/plex\_better\_itunes\_scanner/blob/master/Plex%20Better%20iTunes%20Scanner.py](https://github.com/charliesome/plex_better_itunes_scanner/blob/master/Plex%20Better%20iTunes%20Scanner.py)

Here is an untested attempt at a scanner. i will not maintain it nor test

```
import os, sys, Media
import logging # Python - logging.basicConfig
from mutagen import File as MFile
Virtual = True

### setup logging https://docs.python.org/2/library/logging.html ### #logging.debug/info/warning/error/critical('some critical message: %s', 'highest category')
try: platform = sys.platform.lower() # sys.platform: win32 | darwin | linux2, 
except: platform="other"
# try: platform = Platform.OS.lower() # Platform.OS: Windows, MacOSX, or Linux
# except: p5latform = ""
if (platform == 'win32' or platform == 'windows'): LOG_PATH = os.path.expandvars( '%LOCALAPPDATA%\\Plex Media Server\Logs' )
elif (platform == 'darwin' or platform == 'macosx'): LOG_PATH = os.path.expandvars( '$HOME/Library/Application Support/Plex Media Server\Logs' )
elif 'linux' in platform: LOG_PATH = os.path.expandvars( '$PLEX_HOME/Library/Application Support/Plex Media Server\Logs' )                          
if not os.path.isdir(LOG_PATH): LOG_PATH = os.path.expanduser('~')
logging.basicConfig(filename=os.path.join(LOG_PATH, LOG_FILE), format=LOG_FORMAT, level=logging.DEBUG)  

### Log function ########################################################################################
def Log(entry, filename='Plex Media Scanner Custom.log'): #need relative path
  logging.info(entry + "\r\n") # allow to use windows notepad with correct line feeds
  print entry # when ran from console

### Add files into Plex database ########################################################################
def explore_path(subdir, file_tree):
  files=[]
  for item in os.listdir(subdir): 
    fullpath = os.path.join(subdir, item)
    if os.path.isdir (fullpath): 
      for rx in ignore_dirs_re_findall: ### Skip unwanted folders ###
        result = re.findall(rx, item)   
        if len(result): break
      else: explore_path(fullpath, file_tree)     
    elif os.path.isfile(fullpath):
      fileName, fileExtension = os.path.splitext(item)
      if fileExtension[1:] in video_exts:
        for rx in ignore_files_re_findall: ### Filter trailers and sample files ###
          result = re.findall(rx, item)
          if len(result): 
            Log("'%s' ignore_files_findall: match" % item)
            break
        else: files.append(fullpath) ### Retain wanted file extensions ###
  if not files == []: file_tree[subdir] = files

def Scan(path, files, mediaList, subdirs, language=None, root=None):
  #if not path == "": return # Exit every other iteration than the root scan
  if not root: return
  
  TYPES = {'mp3': 'MP3', 'aac': 'AAC', 'alac': 'ALAC', 'ogg': 'OGG', 'opus': 'Opus', 'flac': 'FLAC',
           'ape': 'APE', 'wv': 'WavPack', 'mpc': 'Musepack', 'asf': 'Windows Media', 'aiff': 'AIFF', }

  Log("=== Scan ================================================================================================================")
  Log("Platform: '%s', Test location: '%s'" % (platform, LOG_PATH))
  Log("Scan: (root: '%s', path='%s', subdirs: '%s', Files: '%s', language: '%s')" % (root if root is not None else "", path, str(subdirs), str(files), language))
  Log("=========================================================================================================================")
  
  file_tree = {} # initialize file_tree
  explore_path(root, file_tree) # initialize file_tree with files on root
  for path, files in file_tree.iteritems(): # Loop to add all series while on the root folder Scan call, which allows subfolders to work
    subdirs=[] # Recreate normal scanner coding: subfolders empty
    path = path.replace(root, "") # Recreate normal scanner coding: path is relative to root
    if path.startswith("/"): path = path[1:] # Recreate normal scanner coding: path doesn't start with "/"   
    relative_path = path.replace(root, " ") # Foe exemple /group/serie/season/ep folder
    reverse_path = Utils.SplitPath(relative_path) # Take top two as show/season, but require at least the top one.
    reverse_path.reverse() # Reverse the order of the folders
  
    for filename in files:
      file, fileExtension = os.path.splitext(filename) # read file extensions
      if fileExtension[1:] not in TYPES: continue # clean out files that are not audio files. if it's not an audio file, we don't want it
      elif type(MFile(filename, None, True)). __name__ is 'OggOpus': # Extract metadata
        plex_track = Media.Track(
          artist = str(MFile(filename, None, True)['ARTIST']).encode("utf-8"), # might need [3:-2] to clean up tags??
          album = str(MFile(filename, None, True)['ALBUM']).encode("utf-8"),
          title = str(MFile(filename, None, True)['TITLE']).encode("utf-8"),
          album_artist = str(MFile(filename, None, True)['ALBUMARTIST']).encode("utf-8"),
          index = str(MFile(filename, None, True)['TRACKNUMBER']).encode("utf-8"),
          disc = str(MFile(filename, None, True)['DISCNUMBER']).encode("utf-8"),
        )
        plex_track.parts.append(filename)
        mediaList.append(plex_track)

```

Best would be to take the original scanner **init**.py and AudioFiles.py and edit relevant parts  
It already accepts opus files, just the supprot for the internal meta has to be added

---

<div class="post-metadata">

**Author:** ![system](https://global.discourse-cdn.com/plex/original/3X/2/a/2acb9765406f63293d357b4ec509ec39aa28f2ad.png) [@system](https://forums.plex.tv/u/system)\
**Post date:** [January 8, 2020, 7:48pm UTC](https://forums.plex.tv/t/opus-scanner/103429/3 "2020-01-08T19:48:58Z")

</div>


