class RarArchive(BaseArchive): def __init__(self, file): self._archive = RarFile(file) def namelist(self): return self._archive.namelist() def filenames(self): return self._archive.namelist() def close(self): return self._archive.close() def is_encrypted(self): return self._archive.needs_password() def extraxt(self): rarfile = rarfile.RarFile.self._archive rarfile.extraxt(self._archive._rarfile) def extractall(self, file): return self._archive.extractall(file) def list(self, *args, **kwargs): self.printdir(*args, **kwargs) def printdir(self, file=None): """Print a table of contents for the zip file.""" print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"), file=file) for file_ in self._archive._file_parser.infolist(): date = "%d-%02d-%02d %02d:%02d:%02d" % file_.date_time[:6] print("%-46s %s %12d" % (file_.filename, date, file_.file_size), file=file)
def scan_archive(path): """Scan an archive from a `path`. :param str path: existing path to the archive. :return: the scanned video. :rtype: :class:`~subliminal.video.Video` """ # check for non-existing path if not os.path.exists(path): raise ValueError('Path does not exist') if not is_rarfile(path): raise ValueError("'{0}' is not a valid archive".format(os.path.splitext(path)[1])) dir_path, filename = os.path.split(path) logger.info('Scanning archive %r in %r', filename, dir_path) # Get filename and file size from RAR rar = RarFile(path) # check that the rar doesnt need a password if rar.needs_password(): raise ValueError('Rar requires a password') # raise an exception if the rar file is broken # must be called to avoid a potential deadlock with some broken rars rar.testrar() file_info = [f for f in rar.infolist() if not f.isdir() and f.filename.endswith(VIDEO_EXTENSIONS)] # sort by file size descending, the largest video in the archive is the one we want, there may be samples or intros file_info.sort(key=operator.attrgetter('file_size'), reverse=True) # no video found if not file_info: raise ValueError('No video in archive') # Free the information about irrelevant files before guessing file_info = file_info[0] # guess video_filename = file_info.filename video_path = os.path.join(dir_path, video_filename) video = Video.fromguess(video_path, guessit(video_path)) # size video.size = file_info.file_size return video
def unrar(path, rar_files, force, result): # pylint: disable=too-many-branches,too-many-statements """ Extracts RAR files :param path: Path to look for files in :param rar_files: Names of RAR files :param force: process currently processing items :param result: Previous results :return: List of unpacked file names """ unpacked_dirs = [] if sickbeard.UNPACK == 1 and rar_files: result.output += log_helper("Packed Releases detected: {0}".format(rar_files), logger.DEBUG) for archive in rar_files: failure = None rar_handle = None try: archive_path = ek(os.path.join, path, archive) if already_processed(path, archive, force, result): result.output += log_helper( "Archive file already post-processed, extraction skipped: {0}".format (archive_path), logger.DEBUG) continue if not helpers.is_rar_file(archive_path): continue result.output += log_helper("Checking if archive is valid and contains a video: {0}".format(archive_path), logger.DEBUG) rar_handle = RarFile(archive_path) if rar_handle.needs_password(): # TODO: Add support in settings for a list of passwords to try here with rar_handle.set_password(x) result.output += log_helper('Archive needs a password, skipping: {0}'.format(archive_path)) continue # rar_handle.testrar() # If there are no video files in the rar, don't extract it rar_media_files = filter(helpers.is_media_file, rar_handle.namelist()) if not rar_media_files: continue rar_release_name = archive.rpartition('.')[0] # Choose the directory we'll unpack to: if sickbeard.UNPACK_DIR and os.path.isdir(sickbeard.UNPACK_DIR): # verify the unpack dir exists unpack_base_dir = sickbeard.UNPACK_DIR else: unpack_base_dir = path if sickbeard.UNPACK_DIR: # Let user know if we can't unpack there result.output += log_helper('Unpack directory cannot be verified. Using {0}'.format(path), logger.DEBUG) # Fix up the list for checking if already processed rar_media_files = [os.path.join(unpack_base_dir, rar_release_name, rar_media_file) for rar_media_file in rar_media_files] skip_rar = False for rar_media_file in rar_media_files: check_path, check_file = os.path.split(rar_media_file) if already_processed(check_path, check_file, force, result): result.output += log_helper( "Archive file already post-processed, extraction skipped: {0}".format (rar_media_file), logger.DEBUG) skip_rar = True break if skip_rar: continue rar_extract_path = ek(os.path.join, unpack_base_dir, rar_release_name) result.output += log_helper("Unpacking archive: {0}".format(archive), logger.DEBUG) rar_handle.extractall(path=rar_extract_path) unpacked_dirs.append(rar_extract_path) except RarCRCError: failure = ('Archive Broken', 'Unpacking failed because of a CRC error') except RarWrongPassword: failure = ('Incorrect RAR Password', 'Unpacking failed because of an Incorrect Rar Password') except PasswordRequired: failure = ('Rar is password protected', 'Unpacking failed because it needs a password') except RarOpenError: failure = ('Rar Open Error, check the parent folder and destination file permissions.', 'Unpacking failed with a File Open Error (file permissions?)') except RarExecError: failure = ('Invalid Rar Archive Usage', 'Unpacking Failed with Invalid Rar Archive Usage. Is unrar installed and on the system PATH?') except BadRarFile: failure = ('Invalid Rar Archive', 'Unpacking Failed with an Invalid Rar Archive Error') except NeedFirstVolume: continue except (Exception, Error) as e: failure = (ex(e), 'Unpacking failed') finally: if rar_handle: del rar_handle if failure: result.output += log_helper('Failed to extract the archive {0}: {1}'.format(archive, failure[0]), logger.WARNING) result.missed_files.append('{0} : Unpacking failed: {1}'.format(archive, failure[1])) result.result = False continue return unpacked_dirs
def unrar(self, path, rar_files, force=False): """ Extract RAR files. :param path: Path to look for files in :param rar_files: Names of RAR files :param force: process currently processing items :return: List of unpacked file names """ unpacked_files = [] if app.UNPACK and rar_files: self.log_and_output('Packed files detected: {rar_files}', level=logging.DEBUG, **{'rar_files': rar_files}) for archive in rar_files: self.log_and_output('Unpacking archive: {archive}', level=logging.DEBUG, **{'archive': archive}) failure = None try: rar_handle = RarFile(os.path.join(path, archive)) # check that the rar doesnt need a password if rar_handle.needs_password(): raise ValueError('Rar requires a password') # Skip extraction if any file in archive has previously been extracted skip_extraction = False for file_in_archive in [ os.path.basename(each.filename) for each in rar_handle.infolist() if not each.isdir() ]: if not force and self.already_postprocessed( file_in_archive): self.log_and_output( 'Archive file already post-processed, extraction skipped: {file_in_archive}', level=logging.DEBUG, **{'file_in_archive': file_in_archive}) skip_extraction = True break if app.POSTPONE_IF_NO_SUBS and os.path.isfile( os.path.join(path, file_in_archive)): self.log_and_output( 'Archive file already extracted, extraction skipped: {file_in_archive}', level=logging.DEBUG, **{'file_in_archive': file_in_archive}) skip_extraction = True break if not skip_extraction: # raise an exception if the rar file is broken rar_handle.testrar() rar_handle.extractall(path=path) for each in rar_handle.infolist(): if not each.isdir(): basename = os.path.basename(each.filename) unpacked_files.append(basename) del rar_handle except (BadRarFile, Error, NotRarFile, RarCannotExec, ValueError) as error: failure = (ex(error), 'Unpacking failed with a Rar error') except Exception as error: failure = (ex(error), 'Unpacking failed for an unknown reason') if failure is not None: self.log_and_output( 'Failed unpacking archive {archive}: {failure}', level=logging.WARNING, **{ 'archive': archive, 'failure': failure[0] }) self.missed_files.append( '{0}: Unpacking failed: {1}'.format( archive, failure[1])) self.result = False continue self.log_and_output('Extracted content: {unpacked_files}', level=logging.DEBUG, **{'unpacked_files': unpacked_files}) return unpacked_files
def add(self, archive_password=None, audio_file=None, session=None, artist_name_fallback=None): cache_key = LibraryUpload.CACHE_KEY % (cherrypy.request.user.id, session) all_tracks = None if not cache.has(cache_key): raise cherrypy.HTTPError(status=409) else: all_tracks = cache.get(cache_key) if audio_file is not None and len(audio_file) == 0: audio_file = None if archive_password is not None and len(archive_password) == 0: archive_password = None content_disposition = cherrypy.request.headers.get('content-disposition') filename = content_disposition[content_disposition.index('filename=') + 9:] if filename.startswith('"') and filename.endswith('"'): filename = filename[1:-1] filename = unquote(filename) ext = os.path.splitext(filename)[1].lower()[1:] basename = os.path.splitext(filename)[0] cache_path = os.path.join(cherrypy.config['opmuse'].get('cache.path'), 'upload') if not os.path.exists(cache_path): os.mkdir(cache_path) tempdir = tempfile.mkdtemp(dir=cache_path) path = os.path.join(tempdir, filename) paths = [] rarfile.PATH_SEP = '/' messages = [] with open(path, 'wb') as fileobj: fileobj.write(cherrypy.request.rfile.read()) # this file is a regular file that belongs to an audio_file if audio_file is not None: track = None tries = 0 # try and sleep until we get the track.. this will almost always # be needed because of the async upload. while track is None and tries < 10: track = library_dao.get_track_by_filename(audio_file.encode('utf8')) tries += 1 if track is None: time.sleep(3) if track is None: messages.append(('warning', ("<strong>%s</strong>: Skipping <strong>%s</strong>, timeout trying to " + "find its track.") % (audio_file, filename))) else: track_structure = TrackStructureParser(track) track_path = track_structure.get_path(absolute=True) relative_track_path = track_structure.get_path(absolute=False).decode('utf8', 'replace') new_path = os.path.join(track_path, filename.encode('utf8')) if os.path.exists(new_path): messages.append(('warning', ("<strong>%s</strong>: Skipping <strong>%s</strong>, already exists " + "in <strong>%s</strong>.") % (audio_file, filename, relative_track_path))) else: shutil.move(path.encode('utf8'), new_path) messages.append(('info', ("<strong>%s</strong>: Uploaded <strong>%s</strong> to " + "<strong>%s</strong>.") % (audio_file, filename, relative_track_path))) elif ext == "zip": # set artist name fallback to zip's name so if it's missing artist tags # it's easily distinguishable and editable so it can be fixed after upload. artist_name_fallback = basename try: zip = ZipFile(path) if archive_password is not None: zip.setpassword(archive_password.encode()) zip.extractall(tempdir) os.remove(path) for name in zip.namelist(): namepath = os.path.join(tempdir, name) # ignore hidden files, e.g. OSX archive weirdness and such if name.startswith(".") or os.path.split(name)[0] == "__MACOSX": shutil.rmtree(namepath) continue paths.append(namepath.encode('utf8')) except Exception as error: messages.append(('danger', "<strong>%s</strong>: %s" % (os.path.basename(path), error))) elif ext == "rar": # look at corresponding ext == zip comment... artist_name_fallback = basename try: rar = RarFile(path) if archive_password is None and rar.needs_password(): messages.append(('danger', "<strong>%s</strong>: Needs password but none provided." % os.path.basename(path))) else: if archive_password is not None: rar.setpassword(archive_password) rar.extractall(tempdir) os.remove(path) for name in rar.namelist(): namepath = os.path.join(tempdir, name) if name.startswith("."): shutil.rmtree(namepath) continue paths.append(namepath.encode('utf8')) except Exception as error: messages.append(('danger', "<strong>%s</strong>: %s" % (os.path.basename(path), error))) # this is a plain audio file else: paths.append(path.encode('utf8')) for path in paths: # update modified time to now, we don't want the time from the zip # archive or whatever os.utime(path, None) if len(paths) > 0: tracks, add_files_messages = library_dao.add_files(paths, move=True, remove_dirs=False, artist_name_fallback=artist_name_fallback, user=cherrypy.request.user) messages += add_files_messages else: tracks = [] shutil.rmtree(tempdir) for track in tracks: all_tracks.append(track.id) if track.album is not None: remotes.update_album(track.album) if track.artist is not None: remotes.update_artist(track.artist) remotes.update_track(track) hierarchy = Library._produce_track_hierarchy(library_dao.get_tracks_by_ids(all_tracks)) return {'hierarchy': hierarchy, 'messages': messages}