1 from datetime import datetime
4 from urllib.parse import quote
6 from archive import settings
7 from django.contrib.auth.decorators import permission_required
8 from django.contrib.postgres.search import SearchVector
9 from django.urls import reverse
10 from django.db.models import Q, Max
11 from django.http import Http404, HttpResponse
12 from django.shortcuts import render, redirect, get_object_or_404
13 from django.utils.translation import gettext as _
14 from django.views.decorators.http import require_POST
15 from django.views.generic import ListView
19 from archive.constants import status
20 from archive import models
21 from archive.forms import AudiobookForm
22 from archive import tasks
23 from archive.utils import all_files
26 def list_new(request):
27 path = settings.NEW_PATH
28 objects = sorted(all_files(path))
29 return render(request, "archive/list_new.html", locals())
32 @permission_required('archive.change_audiobook')
33 def file_new(request, filename):
35 root_filepath = os.path.join(settings.NEW_PATH, filename)
37 form = AudiobookForm(request.POST)
40 form.save(path=filepath)
43 return redirect(list_new)
46 tags = mutagen.File(root_filepath)
53 if isinstance(value, list):
59 d[tag] = models.Project.objects.get(name=d[tag]).pk
60 except models.Project.DoesNotExist:
64 form = AudiobookForm(initial=d)
65 return render(request, "archive/file_new.html", locals())
69 @permission_required('archive.change_audiobook')
70 def move_to_archive(request, filename):
71 """ move a new file to the unmanaged files dir """
73 filename_str = filename.encode('utf-8')
74 old_path = os.path.join(settings.NEW_PATH, filename_str)
75 new_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
76 new_dir = os.path.split(new_path)[0]
77 if not os.path.isdir(new_dir):
80 if not os.path.isfile(old_path):
84 os.link(old_path, new_path)
87 # destination file exists, don't overwrite it
88 # TODO: this should probably be more informative
89 return redirect(file_new, filename)
91 return redirect(list_new)
95 @permission_required('archive.change_audiobook')
96 def remove_to_archive(request, aid):
97 """ move a managed file to the unmanaged files dir """
99 audiobook = get_object_or_404(models.Audiobook, id=aid)
100 old_path = audiobook.source_file.path
101 new_path = os.path.join(settings.UNMANAGED_PATH,
102 str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/'))
103 new_dir = os.path.split(new_path)[0]
104 if not os.path.isdir(new_dir):
107 if not os.path.isfile(old_path):
111 try_new_path = new_path
115 os.link(old_path, try_new_path)
117 # destination file exists, don't overwrite it
119 parts = new_path.rsplit('.', 1)
120 parts[0] += '_%d' % try_number
121 try_new_path = ".".join(parts)
127 return redirect(list_unmanaged)
130 @permission_required('archive.change_audiobook')
131 def move_to_new(request, filename):
132 """ move a unmanaged file to new files dir """
134 filename_str = filename.encode('utf-8')
135 old_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
136 new_path = os.path.join(settings.NEW_PATH, filename_str)
137 new_dir = os.path.split(new_path)[0]
138 if not os.path.isdir(new_dir):
141 if not os.path.isfile(old_path):
145 os.link(old_path, new_path)
148 # destination file exists, don't overwrite it
149 # TODO: this should probably be more informative
150 return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1")
152 return redirect(list_unmanaged)
156 @permission_required('archive.change_audiobook')
157 def publish(request, aid, publish=True):
158 """ mark file for publishing """
159 audiobook = get_object_or_404(models.Audiobook, id=aid)
160 audiobook.publish(request.user, publish=publish)
161 return redirect(file_managed, aid)
165 @permission_required('archive.change_audiobook')
166 def cancel_publishing(request, aid):
167 """ cancel scheduled publishing """
168 audiobook = get_object_or_404(models.Audiobook, id=aid)
170 audiobook.mp3_status = None
171 audiobook.ogg_status = None
172 audiobook.youtube_status = None
173 audiobook.youtube_queued = None
175 return redirect(file_managed, aid)
178 def download(request, aid, which="source"):
179 if which not in ("source", "mp3", "ogg", 'mkv'):
181 audiobook = get_object_or_404(models.Audiobook, id=aid)
185 file_ = getattr(audiobook, "%s_file" % field)
188 ext = file_.path.rsplit('.', 1)[-1]
189 response = HttpResponse(content_type='application/force-download')
191 response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
192 quote(audiobook.title.encode('utf-8'), safe=''), ext)
193 with open(file_.path, 'rb') as f:
194 response.write(f.read())
195 #response['X-Sendfile'] = file_.path.encode('utf-8')
199 def list_publishing(request):
200 objects = models.Audiobook.objects.exclude(
201 mp3_status=None, ogg_status=None, youtube_status=None
202 ).order_by("youtube_queued", "title")
203 objects_by_status = {}
207 statuses.add((o.mp3_status, o.get_mp3_status_display()))
209 statuses.add((o.ogg_status, o.get_ogg_status_display()))
211 statuses.add((o.youtube_status, o.get_youtube_status_display()))
212 for status in statuses:
213 objects_by_status.setdefault(status, []).append(o)
214 status_objects = sorted(objects_by_status.items(), reverse=True)
216 return render(request, "archive/list_publishing.html", locals())
219 class AudiobookList(ListView):
220 def get_queryset(self):
221 qs = models.Audiobook.objects.all()
222 if 's' in self.request.GET:
223 qs = qs.annotate(s=SearchVector('title', 'slug')).filter(s=self.request.GET['s'])
227 @permission_required('archive.change_audiobook')
228 def file_managed(request, id):
229 audiobook = get_object_or_404(models.Audiobook, id=id)
232 form = AudiobookForm(request.POST, instance=audiobook)
240 if audiobook.source_file:
241 path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
244 tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
247 form = AudiobookForm(instance=audiobook)
250 request.user.is_authenticated and
251 request.user.oauthconnection_set.filter(access=True).exists())
254 parts_count = audiobook.parts_count
256 series = models.Audiobook.objects.filter(slug=audiobook.slug)
257 if not audiobook.index:
258 alerts.append(_('There is more than one part, but index is not set.'))
259 if set(series.values_list('index', flat=True)) != set(range(1, parts_count + 1)):
260 alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": parts_count})
262 from youtube.models import YouTube
263 youtube = YouTube.objects.first()
264 youtube_title = youtube.get_title(audiobook)
265 youtube_description = youtube.get_description(audiobook)
268 return render(request, "archive/file_managed.html", locals())
271 def list_unmanaged(request):
272 objects = sorted(all_files(settings.UNMANAGED_PATH))
273 return render(request, "archive/list_unmanaged.html", locals())
276 def file_unmanaged(request, filename):
277 tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
281 err_exists = request.GET.get('exists')
282 return render(request, "archive/file_unmanaged.html", locals())
285 class BookView(ListView):
286 template_name = 'archive/book.html'
288 def get_queryset(self):
289 qs = models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
295 if last_vol is None or last_vol.youtube_volume_index != b.youtube_volume_index:
298 if last_vol_sub is None or b.youtube_volume:
299 last_vol_sub = last_vol
300 last_vol_sub.total_for_sub = 0
301 last_vol.total += b.duration
302 last_vol_sub.total_for_sub += b.duration
303 b.subtotal = last_vol_sub.total_for_sub
307 @permission_required('archive.change_audiobook')
308 def book_youtube_volume(request, aid):
309 audiobook = get_object_or_404(models.Audiobook, id=aid)
310 slug = audiobook.slug
311 cur_vol = audiobook.youtube_volume
312 new_vol = request.POST.get('volume', '')
314 audiobook.youtube_volume = new_vol
317 for a in models.Audiobook.objects.filter(slug=slug, youtube_volume=cur_vol, index__gt=audiobook.index).order_by('index'):
318 if a.youtube_volume != cur_vol:
320 a.youtube_volume = new_vol
323 return redirect('book', audiobook.slug)