Travis Shirk avatar Travis Shirk committed fbaea04 Merge

merged

Comments (0)

Files changed (10)

 18caf92c56dcfb371e29fd715f44fee7366e34b6 v0.7.0-rc3
 0000000000000000000000000000000000000000 v0.7.0-rc3
 dcb1afe9d3532fb9c0cb7da33fc0fb6e0b71462e v0.7.0
+e19be93ecd993683630626d4044eccfc34420acc v0.7.1
+2012-11-25  Travis Shirk  <travis@pobox.com>
+
+	* pavement.py:
+	fixed repo test
+	[ecdc96d901b4] [tip] <stable>
+
+	* src/eyed3/main.py, src/eyed3/plugins/classic.py,
+	src/eyed3/plugins/itunes.py:
+	Moved --backup option to main group so all plugins can use it, like
+	the itunes-podcast plugin now does.
+	[b90179952745] <stable>
+
+	* docs/changelog.rst, fabfile.py:
+	Release notes
+	[78dfa4b9b4af] <stable>
+
+2012-11-24  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/plugins/statistics.py:
+	Fixed stats
+	[fc777b96711b] <stable>
+
+	* src/eyed3/main.py, src/eyed3/utils/cli.py:
+	Colors and stuff
+	[a047c179fd8a] <stable>
+
+	* src/eyed3/plugins/statistics.py:
+	Better stats plugin
+	[2e10611b2b9c] <stable>
+
+	* src/eyed3/plugins/statistics.py:
+	Better counts
+	[a929a697617f] <stable>
+
+	* docs/changelog.rst, src/eyed3/id3/frames.py:
+	doc updates
+	[011d157f90be] <stable>
+
+	* src/eyed3/id3/frames.py:
+	Restored import of FrameHeader in eyed3.id3.frames
+	[c9d618829017] <stable>
+
+	* pavement.py:
+	Merge support for ID3 chapter / table-of-contents. Fixes issue #5.
+	[294209530f8e] <stable>
+
+	* examples/chapters.py, src/eyed3/id3/frames.py, src/eyed3/id3/tag.py:
+	Table of contents Tag accessor
+	[472b6feb4ac5]
+
+2012-11-23  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/id3/frames.py, src/eyed3/id3/tag.py:
+	merge
+	[2acc6e94382f]
+
+	* examples/chapters.py, src/eyed3/id3/frames.py, src/eyed3/id3/tag.py:
+	Accessors for Chapters table of contents, etc.
+	[a0c6eb91efa1]
+
+	* src/eyed3/id3/tag.py, src/eyed3/plugins/classic.py:
+	Some more date fixes and undoings
+	[ea0a2c1e9840] <stable>
+
+	* docs/compliance.rst, docs/index.rst, src/eyed3/id3/tag.py,
+	src/eyed3/plugins/classic.py, src/test/id3/test_tag.py,
+	src/test/test_classic_plugin.py:
+	Dates are still maddening but work better and with better
+	conversions (see compliance.rst)
+	[8e62faac0dc7] <stable>
+
+2012-11-22  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/plugins/classic.py:
+	Added --remove-frame option
+	[bf964eff4469] <stable>
+
+	* src/eyed3/id3/tag.py:
+	Converted frames clean up the existing frames they convert to
+	(TDRC->TYER)
+	[97abda0d9748] <stable>
+
+2012-11-21  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/id3/frames.py:
+	merge
+	[2e54debc9d86]
+
+2012-11-20  Travis Shirk  <travis@pobox.com>
+
+	* examples/echo.py, examples/echo2.py, src/eyed3/id3/frames.py,
+	src/eyed3/id3/tag.py:
+	merge
+	[958a29699c9b]
+
+2012-11-22  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/id3/frames.py:
+	Bug fix
+	[86e3e118386d] <stable>
+
+2012-11-21  Travis Shirk  <travis@pobox.com>
+
+	* docs/changelog.rst, docs/cli.rst, docs/plugins/itunes_plugin.rst,
+	examples/cli_examples.sh, src/eyed3/id3/apple.py,
+	src/eyed3/id3/frames.py, src/eyed3/plugins/itunes.py:
+	iTunes Podcast support and plugin
+	[cc06d3ce1d87] <stable>
+
+2012-11-20  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/core.py, src/eyed3/plugins/__init__.py:
+	Handle 'Z' in frame times (indicated UTC) and more quiet error
+	reporting when a plugin fails to load because of an import error
+	(e.g. not all deps installed)
+	[1f7f0152f51e] <stable>
+
+2012-11-19  Travis Shirk  <travis@pobox.com>
+
+	* docs/cli.rst:
+	update for echo plugins moving
+	[dc13c464c325] <stable>
+
+	* src/eyed3/plugins/__init__.py:
+	LoaderPlugin can now cache the AudioFiles for each directory. Now
+	plugins can choose to deal with directories of audio files rather
+	than only file by file.
+	[d42801be109e] <stable>
+
+	* src/eyed3/id3/frames.py:
+	Don't blow up when non-ascii POPM email address is parsed, set to
+	BOGUS instead.
+	[588435b7e652] <stable>
+
+	* src/eyed3/id3/frames.py, src/eyed3/id3/tag.py,
+	src/eyed3/plugins/classic.py:
+	Native frame type, tag acessor, etc. for POPM - Popularity meter.
+	[c6adb36da49f] <stable>
+
+	* src/eyed3/utils/binfuncs.py:
+	Whoa, when converting 0 to bytes... with the default padding of 0
+	you get '' and not '\x00' :O Changed default padding to 1 to get a 0
+	byte.
+	[4ad8103629ff] <stable>
+
+	* src/eyed3/core.py:
+	docstrings
+	[a6993668f69d] <stable>
+
+	* src/eyed3/utils/__init__.py:
+	Added FileHandler.handleDirectory callback.
+	[b307ee65c75e] <stable>
+
+2012-11-18  Travis Shirk  <travis@pobox.com>
+
+	* examples/echo.py, examples/echo2.py, examples/plugins/echo.py,
+	examples/plugins/echo2.py:
+	All files in plugin search paths are imported (i.e. executed) so the
+	examples needed to be separated.
+	[d7b9c0223025] <stable>
+
+	* src/eyed3/main.py:
+	cleaner import
+	[55af84538436] <stable>
+
+	* examples/chapters.py, src/eyed3/id3/frames.py, src/eyed3/id3/tag.py:
+	Chapters access from Tag, example program, etc.
+	[ee5c95365ada]
+
+	* pavement.py:
+	merge
+	[35cb0078a728]
+
+	* src/eyed3/id3/frames.py:
+	ID3 chapter and table of contents frame support.
+	[c61d1489e379]
+
 2012-11-14  Travis Shirk  <travis@pobox.com>
 
+	* bin/cli_examples.sh, bin/tag_example.py, docs/api.rst,
+	docs/api/id3.rst, docs/api/mp3.rst, docs/api/plugins.rst,
+	docs/api/utils.rst, etc/config.ini, etc/pylint.conf, pavement.py:
+	merge 0.7.0
+	[ffb7f7a4a9e4]
+
+2012-11-18  Travis Shirk  <travis@pobox.com>
+
+	* docs/changelog.rst, docs/conf.py, pavement.py:
+	Updates
+	[0b0358d85b6d] <stable>
+
+	* src/eyed3/plugins/classic.py:
+	No need to convert to unicode, it already is.
+	[551b27333035] <stable>
+
+2012-11-16  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/id3/frames.py:
+	Don't crash for invalid UFID frames. Fixes issue #6.
+	[1558826baa40] <stable>
+
+2012-11-15  Travis Shirk  <travis@pobox.com>
+
+	* src/eyed3/plugins/classic.py:
+	Use LOCAL_ENCODING when converting lyrics files (etc.) to unicode.
+	[ad382aea97d1] <stable>
+
+2012-11-14  Travis Shirk  <travis@pobox.com>
+
+	* .hgtags:
+	Added tag v0.7.0 for changeset dcb1afe9d353
+	[579ca6fd8350] <stable>
+
+	* ChangeLog:
+	prep for release
+	[dcb1afe9d353] [v0.7.0] <stable>
+
 	* docs/changelog.rst, src/eyed3/id3/frames.py:
 	Missing frame class for TSRC
-	[617746e3f65e] [tip] <stable>
+	[617746e3f65e] <stable>
 
 	* docs/changelog.rst, pavement.py:
 	updates

docs/changelog.rst

 .. _release-0.7.1:
 .. _ID3 chapters and table-of-contents: http://www.id3.org/id3v2-chapters-1.0
 
-**0.7.1** - TBD (TBD)
+**0.7.1** - 11.25.2012 (Feel It)
   New Features:
-    * Support for `ID3 chapters and table-of-contents`_ frames
-      (i.e.CHAP and CTOC). Fixes :bbissue:`5`.
+    * [:bbissue:`5`] Support for `ID3 chapters and table-of-contents`_ frames
+      (i.e.CHAP and CTOC).
     * A new plugin for toggling the state of iTunes podcast
       files. In other words, PCST and WFED support. Additionally, the Apple
       "extensions" frames TKWD, TDES, and TGID are supported.
       Also, :class:`eyed3.plugins.LoaderPlugin` can optionally cache the
       loaded audio file objects for each callback to ``handleDirectory``.
     * [classic plugin] New --remove-frame option.
+    * [statistics plugin] More accurate values and easier to extend.
 
   Bug fixes:
     * Fixed a very old bug where certain values of 0 would be written to
       the tag as '' instead of '\x00'.
-    * Don't crash on malformed (invalid) UFID frames. Fixes :bbissue:`6`.
+    * [:bbissue:`6`] Don't crash on malformed (invalid) UFID frames.
     * Handle timestamps that are terminated with 'Z' to show the time is UTC.
     * Conversions between ID3 v2.3 and v2.4 date frames fixed.
     * [classic plugin] Use the system text encoding (locale) when converting
     put("./dist/%s.md5" % os.path.splitext(SRC_DIST_TGZ)[0], RELEASE_D)
 
 def deploy_docs():
-    put("./dist/%s" % DOC_DIST, "~")
-    run("tar xzf %s -C ./www/eyeD3 --strip-components=1" % DOC_DIST)
+    put("./dist/%s" % DOC_DIST, RELEASE_D)
+    run("tar xzf %s -C ./www/eyeD3 --strip-components=1" %
+            os.path.join(RELEASE_D, DOC_DIST))
 
 def deploy():
     deploy_sdist()
 
     print("Checking for clean working copy")
     if not testing:
-        sh('test -z "$(hg status)"')
+        sh('test -z "$(hg status --modified --added --removed --deleted)"')
         sh("hg outgoing | grep 'no changes found'")
         sh("hg incoming | grep 'no changes found'")
 

src/eyed3/main.py

 
 
 def _listPlugins(config):
+    from eyed3.utils.cli import GREEN, GREY, boldText, colorText
+
     print("")
+    def header(name):
+        is_default = name == DEFAULT_PLUGIN
+        return (boldText("* ", c=GREEN if is_default else None) +
+                boldText(name, c=None))
 
     all_plugins = eyed3.plugins.load(reload=True, paths=_getPluginPath(config))
     # Create a new dict for sorted display
         alt_names = plugin.NAMES[1:]
         alt_names = " (%s)" % ", ".join(alt_names) if alt_names else ""
 
-        print("- %s%s:" % (eyed3.utils.cli.boldText(name), alt_names))
-        for l in textwrap.wrap(plugin.SUMMARY):
-            print(l)
+        print("%s %s:" % (header(name), alt_names))
+        for l in textwrap.wrap(plugin.SUMMARY,
+                               initial_indent=' ' * 2,
+                               subsequent_indent=' ' * 2):
+            print(boldText(l, c=GREY))
         print("")
 
 
                        help="Supply a configuration file. The default is "
                             "'%s', although even that is optional." %
                             DEFAULT_CONFIG)
+        p.add_argument("--backup", action="store_true", dest="backup",
+                       help="Plugins should honor this option such that "
+                            "a backup is made of any file modified. The backup "
+                            "is made in same directory with a '.orig' "
+                            "extension added.")
         p.add_argument("-Q", "--quiet", action="store_true", dest="quiet",
                        default=False, help="A hint to plugins to output less.")
-
         p.add_argument("--fs-encoding", action="store",
                        dest="fs_encoding", default=eyed3.LOCAL_FS_ENCODING,
                        metavar="ENCODING",

src/eyed3/plugins/classic.py

 
         # Misc options 
         gid4 = arg_parser.add_argument_group("Misc options")
-        gid4.add_argument("--backup", action="store_true", default=False,
-                          dest="backup", help=ARGS_HELP["--backup"])
         gid4.add_argument("--force-update", action="store_true", default=False,
                           dest="force_update", help=ARGS_HELP["--force-update"])
         gid4.add_argument("-v", "--verbose", action="store_true",
         "--remove-frame": "Remove all frames with the given ID. This option "
                           "may be specified multiple times.",
 
-        "--backup": "Make a backup of any file modified. The backup is made in "
-                    "same directory with a '.orig' extension added.",
         "--force-update": "Rewrite the tag despite there being no edit "
                           "options.",
         "--verbose": "Show all available tag data",

src/eyed3/plugins/itunes.py

 
         if save:
             print("\tAdding...")
-            tag.save()
+            tag.save(backup=self.args.backup)
             self._printStatus(tag)
 
     def _remove(self, tag):
 
         if save:
             print("\tRemoving...")
-            tag.save()
+            tag.save(backup=self.args.backup)
             self._printStatus(tag)
 
     def _printStatus(self, tag):

src/eyed3/plugins/statistics.py

 ################################################################################
 from __future__ import print_function
 import sys, os, operator
+from collections import Counter
 
 from eyed3 import id3
-from eyed3.utils import guessMimetype
-from eyed3.utils.cli import printMsg, printError
+from eyed3.core import AUDIO_MP3
+from eyed3.utils import guessMimetype, cli
 from eyed3.plugins import LoaderPlugin
 
 ID3_VERSIONS = [id3.ID3_V1_0, id3.ID3_V1_1,
                operator.ne: "!=",
               }
 
+class Stat(Counter):
+    TOTAL = "total"
 
-class StatisticsPlugin(LoaderPlugin):
-    NAMES = ['stats']
-    SUMMARY = u"Computes statistics for all audio files scanned."
+    def __init__(self, *args, **kwargs):
+        super(Stat, self).__init__(*args, **kwargs)
+        self[self.TOTAL] = 0
+        self._key_names = {}
 
-    def __init__(self, arg_parser):
-        super(StatisticsPlugin, self).__init__(arg_parser)
+    def compute(self, file, audio_file):
+        self[self.TOTAL] += 1
+        self._compute(file, audio_file)
 
-        self.count = 0
-        self.non_audio_file_count = 0
-        self.hidden_file_count = 0
+    def _compute(self, file, audio_file):
+        pass
 
-        self.versions = {}
+    def report(self):
+        self._report()
+
+    def _sortedKeys(self, most_common=False):
+        def keyDisplayName(k):
+            return self._key_names[k] if k in self._key_names else k
+
+        key_map = {}
+        for k in self.keys():
+            key_map[keyDisplayName(k)] = k
+
+        if not most_common:
+            sorted_names = list(key_map.keys())
+            sorted_names.remove(self.TOTAL)
+            sorted_names.sort()
+            sorted_names.append(self.TOTAL)
+        else:
+            most_common = self.most_common()
+            sorted_names = []
+            remainder_names = []
+            for k, v in most_common:
+                if k != self.TOTAL and v > 0:
+                    sorted_names.append(keyDisplayName(k))
+                elif k != self.TOTAL:
+                    remainder_names.append(keyDisplayName(k))
+
+            remainder_names.sort()
+            sorted_names = sorted_names + remainder_names
+            sorted_names.append(self.TOTAL)
+
+        return [key_map[name] for name in sorted_names]
+
+    def _report(self, most_common=False):
+        keys = self._sortedKeys(most_common=most_common)
+
+        key_col_width = 0
+        val_col_width = 0
+        for key in keys:
+            key = self._key_names[key] if key in self._key_names else key
+            key_col_width = max(key_col_width, len(str(key)))
+            val_col_width = max(val_col_width, len(str(self[key])))
+        key_col_width += 1
+        val_col_width += 1
+
+        for k in keys:
+            key_name = self._key_names[k] if k in self._key_names else k
+            value = self[k]
+            percent = self.percent(k) if value and k != "total" else ""
+            print("%(padding)s%(key)s:%(value)s%(percent)s" %
+                  { "padding": ' ' * 4,
+                    "key":   str(key_name).ljust(key_col_width),
+                    "value": str(value).rjust(val_col_width),
+                    "percent": " ( %s%.2f%%%s )" %
+                                 (cli.GREEN, percent, cli.RESET) if percent
+                                                                 else "",
+                  })
+
+    def percent(self, key):
+        return (float(self[key]) / float(self["total"])) * 100
+
+
+class AudioStat(Stat):
+    def compute(self, audio_file):
+        assert(audio_file)
+        self["total"] += 1
+        self._compute(audio_file)
+
+    def _compute(self, audio_file):
+        pass
+
+
+class FileCounterStat(Stat):
+    def __init__(self):
+        super(FileCounterStat, self).__init__()
+        for k in ("audio", "hidden", "audio (other)"):
+            self[k] = 0
+
+    def _compute(self, file, audio_file):
+        if audio_file:
+            self["audio"] += 1
+        if os.path.basename(file).startswith('.'):
+            self["hidden"] += 1
+        mt = guessMimetype(file)
+        if mt and mt.startswith("audio/") and not audio_file:
+            self["unsupported (other)"] += 1
+
+    def _report(self):
+        print(cli.BOLD + cli.GREY + "Files:" + cli.RESET)
+        super(FileCounterStat, self)._report()
+
+
+class MimeTypeStat(Stat):
+    def _compute(self, file, audio_file):
+        mt = guessMimetype(file)
+        self[mt] += 1
+
+    def _report(self):
+        print(cli.BOLD + cli.GREY + "Mime-Types:" + cli.RESET)
+        super(MimeTypeStat, self)._report(most_common=True)
+
+
+class Id3VersionCounter(AudioStat):
+    def __init__(self):
+        super(Id3VersionCounter, self).__init__()
         for v in ID3_VERSIONS:
-            self.versions[v] = 0
+            self[v] = 0
+            self._key_names[v] = id3.versionToString(v)
 
-        self.bitrates = {}
-        self.bitrates["cbr"] = 0
-        self.bitrates["vbr"] = 0
+    def _compute(self, audio_file):
+        if audio_file.tag:
+            self[audio_file.tag.version] += 1
+        else:
+            self[None] += 1
+
+    def _report(self):
+        print(cli.BOLD + cli.GREY + "ID3 versions:" + cli.RESET)
+        super(Id3VersionCounter, self)._report()
+
+
+class BitrateCounter(AudioStat):
+    def __init__(self):
+        super(BitrateCounter, self).__init__()
+        self["cbr"] = 0
+        self["vbr"] = 0
         self.bitrate_keys = [(operator.le, 96),
                              (operator.le, 112),
                              (operator.le, 128),
                              (operator.gt, 320),
                             ]
         for k in self.bitrate_keys:
-            self.bitrates[k] = 0
+            self[k] = 0
+            op, bitrate = k
+            self._key_names[k] = "%s %d" % (_OP_STRINGS[op], bitrate)
 
-        self.mts = {}
+    def _compute(self, audio_file):
+        if audio_file.type != AUDIO_MP3 or audio_file.info is None:
+            self["total"] -=1
+            return
+
+        vbr, br =  audio_file.info.bit_rate
+        if vbr:
+            self["vbr"] += 1
+        else:
+            self["cbr"] += 1
+
+        for key in self.bitrate_keys:
+            key_op, key_br = key
+            if key_op(br, key_br):
+                self[key] += 1
+                break
+
+    def _report(self):
+        print(cli.BOLD + cli.GREY + "MP3 bitrates:" + cli.RESET)
+        super(BitrateCounter, self)._report(most_common=True)
+
+    def _sortedKeys(self, most_common=False):
+        keys = super(BitrateCounter, self)._sortedKeys(most_common=most_common)
+        keys.remove("cbr")
+        keys.remove("vbr")
+        keys.insert(0, "cbr")
+        keys.insert(1, "vbr")
+        return keys
+
+
+class StatisticsPlugin(LoaderPlugin):
+    NAMES = ['stats']
+    SUMMARY = u"Computes statistics for all audio files scanned."
+
+    def __init__(self, arg_parser):
+        super(StatisticsPlugin, self).__init__(arg_parser)
+        self._stats = []
+
+        self.file_counter = FileCounterStat()
+        self._stats.append(self.file_counter)
+
+        self.mt_stat = MimeTypeStat()
+        self._stats.append(self.mt_stat)
+
+        self.id3_version_counter = Id3VersionCounter()
+        self._stats.append(self.id3_version_counter)
+
+        self.bitrates = BitrateCounter()
+        self._stats.append(self.bitrates)
 
     def handleFile(self, f):
         super(StatisticsPlugin, self).handleFile(f)
+        sys.stdout.write('.')
+        sys.stdout.flush()
 
-        # mimetype stats
-        mt = guessMimetype(f)
-        if mt in self.mts:
-            self.mts[mt] += 1
-        else:
-            self.mts[mt] = 1
-
-        if self.audio_file and self.audio_file.tag:
-            self.count += 1
-            if os.path.basename(f).startswith('.'):
-                self.hidden_file_count += 1
-
-            # ID3 versions
-            id3_version = self.audio_file.tag.version
-            self.versions[id3_version] += 1
-            sys.stdout.write('.')
-            sys.stdout.flush()
-
-            # mp3 bit rates
-            vbr, br =  self.audio_file.info.bit_rate
-            if vbr:
-                self.bitrates["vbr"] += 1
+        for stat in self._stats:
+            if isinstance(stat, AudioStat):
+                if self.audio_file:
+                    stat.compute(self.audio_file)
             else:
-                self.bitrates["cbr"] += 1
-            for key in self.bitrate_keys:
-                key_op, key_br = key
-                if key_op(br, key_br):
-                    self.bitrates[key] += 1
-                    break
+                stat.compute(f, self.audio_file)
 
     def handleDone(self):
-        print("\nAnalyzed %d audio files (%d non-audio) (%d hidden)" %
-              (self.count, self.non_audio_file_count, self.hidden_file_count))
+        print("\n")
+        for stat in self._stats:
+            stat.report()
+            print("\n")
+        print()
 
-        if not self.count:
-            return
-
-        printMsg("\nMime-types:")
-        types = list(self.mts.keys())
-        types.sort()
-        for t in types:
-            count = self.mts[t]
-            percent = (float(count) / float(self.count)) * 100
-            printMsg("\t%s:%s (%%%.2f)" % (str(t).ljust(12),
-                                           str(count).rjust(8),
-                                           percent))
-
-        print("\nMP3 bitrates:")
-        for key in ["cbr", "vbr"]:
-            val = self.bitrates[key]
-            print("\t%s   : %d \t%.2f%%" %
-                  (key, val, (float(val) / float(self.count)) * 100))
-
-        for key in self.bitrate_keys:
-            val = self.bitrates[key]
-            key_op, key_br = key
-            print("\t%s%03d : %d \t%.2f%%" %
-                  (_OP_STRINGS[key_op], key_br, val,
-                   (float(val) / float(self.count)) * 100))
-
-        print("\nID3 versions:")
-        for v in ID3_VERSIONS:
-            v_count = self.versions[v]
-            v_percent = (float(v_count) / float(self.count)) * 100
-            print("\t%s : %d \t%.2f%%" % (id3.versionToString(v),
-                                          v_count, v_percent))
-
-

src/eyed3/utils/cli.py

     return "%s%s%s%s" % (getColor(BOLD, fp), getColor(c, fp),
                          s, getColor(RESET, fp))
 
+@utils.encodeUnicode()
+def colorText(s, fp=sys.stdout, c=None):
+    return getColor(c, fp) + s + getColor(RESET)
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.