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)
161 'name': audiobook.title,
162 'url': audiobook.url,
163 'tags': audiobook.new_publish_tags(),
165 audiobook.set_mp3_tags(tags)
166 audiobook.set_ogg_tags(tags)
167 audiobook.mp3_status = audiobook.ogg_status = status.WAITING
169 # isn't there a race here?
170 audiobook.mp3_task = tasks.Mp3Task.delay(request.user.id, aid, publish).task_id
171 audiobook.ogg_task = tasks.OggTask.delay(request.user.id, aid, publish).task_id
174 return redirect(file_managed, aid)
178 @permission_required('archive.change_audiobook')
179 def cancel_publishing(request, aid):
180 """ cancel scheduled publishing """
181 audiobook = get_object_or_404(models.Audiobook, id=aid)
183 audiobook.mp3_status = None
184 audiobook.ogg_status = None
185 audiobook.youtube_status = None
186 audiobook.youtube_queued = None
188 return redirect(file_managed, aid)
191 def download(request, aid, which="source"):
192 if which not in ("source", "mp3", "ogg", 'mkv'):
194 audiobook = get_object_or_404(models.Audiobook, id=aid)
198 file_ = getattr(audiobook, "%s_file" % field)
201 ext = file_.path.rsplit('.', 1)[-1]
202 response = HttpResponse(content_type='application/force-download')
204 response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
205 quote(audiobook.title.encode('utf-8'), safe=''), ext)
206 with open(file_.path, 'rb') as f:
207 response.write(f.read())
208 #response['X-Sendfile'] = file_.path.encode('utf-8')
212 def list_publishing(request):
213 objects = models.Audiobook.objects.exclude(
214 mp3_status=None, ogg_status=None, youtube_status=None
215 ).order_by("youtube_queued", "title")
216 objects_by_status = {}
220 statuses.add((o.mp3_status, o.get_mp3_status_display()))
222 statuses.add((o.ogg_status, o.get_ogg_status_display()))
224 statuses.add((o.youtube_status, o.get_youtube_status_display()))
225 for status in statuses:
226 objects_by_status.setdefault(status, []).append(o)
227 status_objects = sorted(objects_by_status.items(), reverse=True)
229 return render(request, "archive/list_publishing.html", locals())
232 class AudiobookList(ListView):
233 def get_queryset(self):
234 qs = models.Audiobook.objects.all()
235 if 's' in self.request.GET:
236 qs = qs.annotate(s=SearchVector('title', 'slug')).filter(s=self.request.GET['s'])
240 @permission_required('archive.change_audiobook')
241 def file_managed(request, id):
242 audiobook = get_object_or_404(models.Audiobook, id=id)
245 form = AudiobookForm(request.POST, instance=audiobook)
253 if audiobook.source_file:
254 path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
257 tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
260 form = AudiobookForm(instance=audiobook)
263 request.user.is_authenticated and
264 request.user.oauthconnection_set.filter(access=True).exists())
267 parts_count = audiobook.parts_count
269 series = models.Audiobook.objects.filter(slug=audiobook.slug)
270 if not audiobook.index:
271 alerts.append(_('There is more than one part, but index is not set.'))
272 if set(series.values_list('index', flat=True)) != set(range(1, parts_count + 1)):
273 alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": parts_count})
275 from youtube.models import YouTube
276 youtube = YouTube.objects.first()
277 youtube_title = youtube.get_title(audiobook)
278 youtube_description = youtube.get_description(audiobook)
281 return render(request, "archive/file_managed.html", locals())
284 def list_unmanaged(request):
285 objects = sorted(all_files(settings.UNMANAGED_PATH))
286 return render(request, "archive/list_unmanaged.html", locals())
289 def file_unmanaged(request, filename):
290 tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
294 err_exists = request.GET.get('exists')
295 return render(request, "archive/file_unmanaged.html", locals())
298 class BookView(ListView):
299 template_name = 'archive/book.html'
301 def get_queryset(self):
302 qs = models.Audiobook.objects.filter(slug=self.kwargs["slug"]).order_by(
307 if last_vol is None or last_vol.youtube_volume != b.youtube_volume:
310 last_vol.total += b.duration
314 @permission_required('archive.change_audiobook')
315 def book_youtube_volume(request, aid):
316 audiobook = get_object_or_404(models.Audiobook, id=aid)
317 slug = audiobook.slug
318 cur_vol = audiobook.youtube_volume
319 new_vol = request.POST.get('volume', '')
321 audiobook.youtube_volume = new_vol
324 for a in models.Audiobook.objects.filter(youtube_volume=cur_vol, index__gt=audiobook.index).order_by('index'):
325 if a.youtube_volume != cur_vol:
327 a.youtube_volume = new_vol
330 return redirect('book', audiobook.slug)