mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00

The media table was originally introduced when Anki hashed media filenames, and needed a way to remember the original filename. It also helped with: 1) getting a quick list of all media used in the deck, or the media added since the last sync, for mobile clients 2) merging identical files with different names But had some drawbacks: - every operation that modifies templates, models or facts meant generating the q/a and checking if any new media had appeared - each entry is about 70 bytes, and some decks have 100k+ media files So we remove the media table. We address 1) by being more intelligent about media downloads on the mobile platform. We ask the user after a full sync if they want to look for missing media, and they can choose not to if they know they haven't added any. And on a partial sync, we can scan the contents of the incoming facts for media references, and download any references we find. This also avoids all the issues people had with media not downloading because it was in their media folder but not in the media database. For 2), when copying media to the media folder, if we have a duplicate filename, we check if that file has the same md5, and avoid copying if so. This won't merge identical content that has separate names, but instances where users need that are rare.
130 lines
4.7 KiB
Python
130 lines
4.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
|
|
|
import re, tempfile, os, sys, shutil, cgi, subprocess
|
|
from anki.utils import checksum, call
|
|
from anki.hooks import addHook
|
|
from htmlentitydefs import entitydefs
|
|
from anki.lang import _
|
|
|
|
latexDviPngCmd = ["dvipng", "-D", "200", "-T", "tight"]
|
|
|
|
regexps = {
|
|
"standard": re.compile(r"\[latex\](.+?)\[/latex\]", re.DOTALL | re.IGNORECASE),
|
|
"expression": re.compile(r"\[\$\](.+?)\[/\$\]", re.DOTALL | re.IGNORECASE),
|
|
"math": re.compile(r"\[\$\$\](.+?)\[/\$\$\]", re.DOTALL | re.IGNORECASE),
|
|
}
|
|
|
|
tmpdir = tempfile.mkdtemp(prefix="anki")
|
|
|
|
# add standard tex install location to osx
|
|
if sys.platform == "darwin":
|
|
os.environ['PATH'] += ":/usr/texbin"
|
|
|
|
def renderLatex(deck, text, build=True):
|
|
"Convert TEXT with embedded latex tags to image links."
|
|
for match in regexps['standard'].finditer(text):
|
|
text = text.replace(match.group(), imgLink(deck, match.group(1),
|
|
build))
|
|
for match in regexps['expression'].finditer(text):
|
|
text = text.replace(match.group(), imgLink(
|
|
deck, "$" + match.group(1) + "$", build))
|
|
for match in regexps['math'].finditer(text):
|
|
text = text.replace(match.group(), imgLink(
|
|
deck,
|
|
"\\begin{displaymath}" + match.group(1) + "\\end{displaymath}",
|
|
build))
|
|
return text
|
|
|
|
def stripLatex(text):
|
|
for match in regexps['standard'].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
for match in regexps['expression'].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
for match in regexps['math'].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
return text
|
|
|
|
def latexImgFile(deck, latexCode):
|
|
key = checksum(latexCode)
|
|
return "latex-%s.png" % key
|
|
|
|
def mungeLatex(deck, latex):
|
|
"Convert entities, fix newlines, convert to utf8, and wrap pre/postamble."
|
|
for match in re.compile("&([a-z]+);", re.IGNORECASE).finditer(latex):
|
|
if match.group(1) in entitydefs:
|
|
latex = latex.replace(match.group(), entitydefs[match.group(1)])
|
|
latex = re.sub("<br( /)?>", "\n", latex)
|
|
latex = (deck.getVar("latexPre") + "\n" +
|
|
latex + "\n" +
|
|
deck.getVar("latexPost"))
|
|
latex = latex.encode("utf-8")
|
|
return latex
|
|
|
|
def buildImg(deck, latex):
|
|
log = open(os.path.join(tmpdir, "latex_log.txt"), "w+")
|
|
texpath = os.path.join(tmpdir, "tmp.tex")
|
|
texfile = file(texpath, "w")
|
|
texfile.write(latex)
|
|
texfile.close()
|
|
# make sure we have a valid mediaDir
|
|
mdir = deck.media.dir(create=True)
|
|
oldcwd = os.getcwd()
|
|
if sys.platform == "win32":
|
|
si = subprocess.STARTUPINFO()
|
|
try:
|
|
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
except:
|
|
si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
|
|
else:
|
|
si = None
|
|
try:
|
|
os.chdir(tmpdir)
|
|
def errmsg(type):
|
|
msg = _("Error executing %s.\n") % type
|
|
try:
|
|
log = open(os.path.join(tmpdir, "latex_log.txt")).read()
|
|
msg += "<small><pre>" + cgi.escape(log) + "</pre></small>"
|
|
except:
|
|
msg += _("Have you installed latex and dvipng?")
|
|
pass
|
|
return msg
|
|
if call(["latex", "-interaction=nonstopmode",
|
|
"tmp.tex"], stdout=log, stderr=log, startupinfo=si):
|
|
return (False, errmsg("latex"))
|
|
if call(latexDviPngCmd + ["tmp.dvi", "-o", "tmp.png"],
|
|
stdout=log, stderr=log, startupinfo=si):
|
|
return (False, errmsg("dvipng"))
|
|
# add to media
|
|
target = latexImgFile(deck, latex)
|
|
shutil.copy2(os.path.join(tmpdir, "tmp.png"),
|
|
os.path.join(mdir, target))
|
|
return (True, target)
|
|
finally:
|
|
os.chdir(oldcwd)
|
|
|
|
def imageForLatex(deck, latex, build=True):
|
|
"Return an image that represents 'latex', building if necessary."
|
|
imageFile = latexImgFile(deck, latex)
|
|
ok = True
|
|
if build and (not imageFile or not os.path.exists(imageFile)):
|
|
(ok, imageFile) = buildImg(deck, latex)
|
|
if not ok:
|
|
return (False, imageFile)
|
|
return (True, imageFile)
|
|
|
|
def imgLink(deck, latex, build=True):
|
|
"Parse LATEX and return a HTML image representing the output."
|
|
munged = mungeLatex(deck, latex)
|
|
(ok, img) = imageForLatex(deck, munged, build)
|
|
if ok:
|
|
return '<img src="%s" alt="%s">' % (img, latex)
|
|
else:
|
|
return img
|
|
|
|
def formatQA(html, type, cid, mid, fact, tags, cm, deck):
|
|
return renderLatex(deck, html)
|
|
|
|
# setup q/a filter
|
|
addHook("formatQA", formatQA)
|